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