mirror of
https://github.com/2dust/v2rayN.git
synced 2026-09-13 18:02:07 +03:00
Optimize ViewModel activation lifecycle (#10146)
* Optimize ViewModel activation lifecycle * Fix * Fix * Fix * Set period to 1s
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
namespace ServiceLib.Base;
|
||||
|
||||
public class MyReactiveObject : ReactiveObject
|
||||
public class MyReactiveObject : ReactiveObject, IActivatableViewModel
|
||||
{
|
||||
protected static Config? _config;
|
||||
|
||||
public ViewModelActivator Activator { get; } = new();
|
||||
}
|
||||
|
||||
@@ -136,29 +136,23 @@ public static class Extension
|
||||
.Replace("\n", replacement);
|
||||
}
|
||||
|
||||
public static async Task<TOutput> HandleSafe<TInput, TOutput>(
|
||||
this Interaction<TInput, TOutput> interaction,
|
||||
TInput input,
|
||||
public static IObservable<TOutput> HandleSafe<TInput, TOutput>(
|
||||
this Interaction<TInput, TOutput> interaction, TInput input,
|
||||
TOutput defaultValue = default!,
|
||||
[CallerMemberName] string memberName = "",
|
||||
[CallerFilePath] string filePath = "",
|
||||
[CallerLineNumber] int lineNumber = 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await interaction.Handle(input);
|
||||
}
|
||||
catch (UnhandledInteractionException<TInput, TOutput> ex)
|
||||
{
|
||||
var title = $"Unhandled interaction exception in {memberName} at {filePath}:{lineNumber}";
|
||||
Logging.SaveLog(title, ex);
|
||||
return defaultValue;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var title = $"Exception occurred while handling interaction in {memberName} at {filePath}:{lineNumber}, input: {input}";
|
||||
Logging.SaveLog(title, ex);
|
||||
return defaultValue;
|
||||
}
|
||||
return Signal.Defer(() => interaction.Handle(input))
|
||||
.Catch<TOutput, UnhandledInteractionException<TInput, TOutput>>(ex =>
|
||||
{
|
||||
Logging.SaveLog($"Unhandled interaction exception in {memberName} at {filePath}:{lineNumber}", ex);
|
||||
return Signal.Return(defaultValue);
|
||||
})
|
||||
.Catch<TOutput, Exception>(ex =>
|
||||
{
|
||||
Logging.SaveLog($"Exception occurred while handling interaction in {memberName} at {filePath}:{lineNumber}, input: {input}", ex);
|
||||
return Signal.Return(defaultValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ public class ClashUIItem
|
||||
public bool EnableMixinContent { get; set; }
|
||||
public int ProxiesSorting { get; set; }
|
||||
public bool ProxiesAutoRefresh { get; set; }
|
||||
public int ProxiesAutoDelayTestInterval { get; set; } = 10;
|
||||
public int ProxiesRefreshInterval { get; set; } = 2;
|
||||
public bool ConnectionsAutoRefresh { get; set; }
|
||||
public int ConnectionsRefreshInterval { get; set; } = 2;
|
||||
public List<ColumnItem> ConnectionsColumnItem { get; set; }
|
||||
|
||||
@@ -24,12 +24,19 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
await ClashConnectionClose(true);
|
||||
});
|
||||
|
||||
_ = Task.Factory.StartNew(
|
||||
async () => await GetClashConnectionsTask(),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
var cancelDisposable = new CancellationDisposable();
|
||||
cancelDisposable.DisposeWith(disposables);
|
||||
var token = cancelDisposable.Token;
|
||||
|
||||
Task.Factory.StartNew(
|
||||
async () => await GetClashConnectionsTask(token),
|
||||
token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public BulkObservableCollection<ClashConnectionModel> ConnectionItems { get; } = [];
|
||||
@@ -117,29 +124,40 @@ public partial class ClashConnectionsViewModel : MyReactiveObject
|
||||
await GetClashConnections();
|
||||
}
|
||||
|
||||
public async Task GetClashConnectionsTask()
|
||||
public async Task GetClashConnectionsTask(CancellationToken token = default)
|
||||
{
|
||||
var numOfExecuted = 1;
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
await Task.Delay(1000 * 5);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
var numOfExecuted = 1;
|
||||
while (true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await Task.Delay(1000, token);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (_config.ClashUIItem.ConnectionsRefreshInterval <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0)
|
||||
{
|
||||
continue;
|
||||
if (numOfExecuted % _config.ClashUIItem.ConnectionsRefreshInterval != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await GetClashConnections();
|
||||
}
|
||||
await GetClashConnections();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GetClashConnectionsTask", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +55,19 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
|
||||
#endregion WhenAnyValue && ReactiveCommand
|
||||
|
||||
_ = Task.Factory.StartNew(
|
||||
async () => await GetClashProxiesTask(),
|
||||
CancellationToken.None,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
var cancelDisposable = new CancellationDisposable();
|
||||
cancelDisposable.DisposeWith(disposables);
|
||||
var token = cancelDisposable.Token;
|
||||
|
||||
Task.Factory.StartNew(
|
||||
async () => await GetClashProxiesTask(token),
|
||||
token,
|
||||
TaskCreationOptions.LongRunning,
|
||||
TaskScheduler.Default
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
public BulkObservableCollection<ClashProxyModel> ProxyGroups { get; } = [];
|
||||
@@ -101,27 +108,40 @@ public partial class ClashProxiesViewModel : MyReactiveObject
|
||||
|
||||
#region task
|
||||
|
||||
public async Task GetClashProxiesTask()
|
||||
public async Task GetClashProxiesTask(CancellationToken token = default)
|
||||
{
|
||||
var numOfExecuted = 1;
|
||||
while (true)
|
||||
try
|
||||
{
|
||||
await Task.Delay(1000 * 60);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
var numOfExecuted = 1;
|
||||
while (true)
|
||||
{
|
||||
continue;
|
||||
await Task.Delay(1000, token);
|
||||
numOfExecuted++;
|
||||
if (!(AutoRefresh && AppManager.Instance.ShowInTaskbar &&
|
||||
AppManager.Instance.IsRunningCore(ECoreType.sing_box)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_config.ClashUIItem.ProxiesRefreshInterval <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (numOfExecuted % _config.ClashUIItem.ProxiesRefreshInterval != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await GetClashProxies();
|
||||
}
|
||||
if (_config.ClashUIItem.ProxiesAutoDelayTestInterval <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (numOfExecuted % _config.ClashUIItem.ProxiesAutoDelayTestInterval != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
await GetClashProxies();
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Ignored
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logging.SaveLog("GetClashProxiesTask", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,11 @@ namespace ServiceLib.ViewModels;
|
||||
|
||||
public partial class MsgViewModel : MyReactiveObject
|
||||
{
|
||||
public Interaction<string, RxVoid> DispatcherShowMsgInteraction { get; } = new();
|
||||
public Interaction<string, RxVoid> ShowMsgInteraction { get; } = new();
|
||||
|
||||
private readonly ConcurrentQueue<string> _queueMsg = new();
|
||||
private volatile bool _lastMsgFilterNotAvailable;
|
||||
private int _showLock = 0; // 0 = unlocked, 1 = locked
|
||||
public int NumMaxMsg { get; } = 500;
|
||||
public int NumMaxMsg => 500;
|
||||
|
||||
[Reactive]
|
||||
public partial string MsgFilter { get; set; }
|
||||
@@ -30,59 +29,39 @@ public partial class MsgViewModel : MyReactiveObject
|
||||
|
||||
AppEvents.SendMsgViewRequested
|
||||
.AsObservable()
|
||||
//.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(content => _ = AppendQueueMsg(content));
|
||||
.Subscribe(EnqueueQueueMsg);
|
||||
|
||||
this.WhenActivated(disposables =>
|
||||
{
|
||||
Signal.Every(TimeSpan.FromSeconds(1))
|
||||
.Where(_ => AutoRefresh && AppManager.Instance.ShowInTaskbar)
|
||||
.ObserveOn(RxSchedulers.MainThreadScheduler)
|
||||
.Subscribe(_ => FlushQueueToView())
|
||||
.DisposeWith(disposables);
|
||||
});
|
||||
}
|
||||
|
||||
public void FlushQueueMsg()
|
||||
private void FlushQueueToView()
|
||||
{
|
||||
_ = AppendQueueMsg(string.Empty);
|
||||
}
|
||||
|
||||
private async Task AppendQueueMsg(string msg)
|
||||
{
|
||||
if (AutoRefresh == false)
|
||||
if (!AutoRefresh || _queueMsg.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EnqueueQueueMsg(msg);
|
||||
|
||||
if (!AppManager.Instance.ShowInTaskbar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (Interlocked.CompareExchange(ref _showLock, 1, 0) != 0)
|
||||
var sb = new StringBuilder();
|
||||
while (_queueMsg.TryDequeue(out var msg))
|
||||
{
|
||||
return;
|
||||
sb.Append(msg);
|
||||
}
|
||||
|
||||
try
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
await Task.Delay(500).ConfigureAwait(false);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
while (_queueMsg.TryDequeue(out var line))
|
||||
{
|
||||
sb.Append(line);
|
||||
}
|
||||
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
await DispatcherShowMsgInteraction.Handle(sb.ToString());
|
||||
}
|
||||
catch
|
||||
{
|
||||
_queueMsg.Enqueue(sb.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
Interlocked.Exchange(ref _showLock, 0);
|
||||
ShowMsgInteraction.HandleSafe(sb.ToString()).Subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,11 +89,11 @@ public partial class MsgViewModel : MyReactiveObject
|
||||
}
|
||||
}
|
||||
|
||||
EnqueueWithLimit(msg);
|
||||
if (!msg.EndsWith(Environment.NewLine))
|
||||
{
|
||||
EnqueueWithLimit(Environment.NewLine);
|
||||
}
|
||||
var formattedMsg = msg.EndsWith(Environment.NewLine)
|
||||
? msg
|
||||
: msg + Environment.NewLine;
|
||||
|
||||
EnqueueWithLimit(formattedMsg);
|
||||
}
|
||||
|
||||
private void EnqueueWithLimit(string item)
|
||||
|
||||
@@ -16,15 +16,12 @@ public partial class MsgView : ReactiveUserControl<MsgViewModel>
|
||||
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||
|
||||
ViewModel.DispatcherShowMsgInteraction.RegisterHandler(interaction =>
|
||||
ViewModel.ShowMsgInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
var msg = interaction.Input;
|
||||
Dispatcher.UIThread.Post(() => ShowMsg(msg),
|
||||
DispatcherPriority.ApplicationIdle);
|
||||
ShowMsg(msg);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel?.FlushQueueMsg();
|
||||
});
|
||||
|
||||
TextEditorKeywordHighlighter.Attach(txtMsg, Global.LogLevelColors.ToDictionary(
|
||||
|
||||
@@ -11,17 +11,12 @@ public partial class MsgView
|
||||
this.Bind(ViewModel, vm => vm.MsgFilter, v => v.cmbMsgFilter.Text).DisposeWith(disposables);
|
||||
this.Bind(ViewModel, vm => vm.AutoRefresh, v => v.togAutoRefresh.IsChecked).DisposeWith(disposables);
|
||||
|
||||
ViewModel.DispatcherShowMsgInteraction.RegisterHandler(interaction =>
|
||||
ViewModel.ShowMsgInteraction.RegisterHandler(interaction =>
|
||||
{
|
||||
var msg = interaction.Input;
|
||||
Application.Current?.Dispatcher.Invoke(() =>
|
||||
{
|
||||
ShowMsg(msg);
|
||||
}, DispatcherPriority.ApplicationIdle);
|
||||
ShowMsg(msg);
|
||||
interaction.SetOutput(RxVoid.Default);
|
||||
}).DisposeWith(disposables);
|
||||
|
||||
ViewModel?.FlushQueueMsg();
|
||||
});
|
||||
|
||||
btnCopy.Click += menuMsgViewCopyAll_Click;
|
||||
|
||||
Reference in New Issue
Block a user