diff --git a/v2rayN/ServiceLib/Base/MyReactiveObject.cs b/v2rayN/ServiceLib/Base/MyReactiveObject.cs index aa0758e9..77f05559 100644 --- a/v2rayN/ServiceLib/Base/MyReactiveObject.cs +++ b/v2rayN/ServiceLib/Base/MyReactiveObject.cs @@ -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(); } diff --git a/v2rayN/ServiceLib/Common/Extension.cs b/v2rayN/ServiceLib/Common/Extension.cs index d68d5403..f9ccdc58 100644 --- a/v2rayN/ServiceLib/Common/Extension.cs +++ b/v2rayN/ServiceLib/Common/Extension.cs @@ -136,29 +136,23 @@ public static class Extension .Replace("\n", replacement); } - public static async Task HandleSafe( - this Interaction interaction, - TInput input, + public static IObservable HandleSafe( + this Interaction interaction, TInput input, TOutput defaultValue = default!, [CallerMemberName] string memberName = "", [CallerFilePath] string filePath = "", [CallerLineNumber] int lineNumber = 0) { - try - { - return await interaction.Handle(input); - } - catch (UnhandledInteractionException 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>(ex => + { + Logging.SaveLog($"Unhandled interaction exception in {memberName} at {filePath}:{lineNumber}", ex); + return Signal.Return(defaultValue); + }) + .Catch(ex => + { + Logging.SaveLog($"Exception occurred while handling interaction in {memberName} at {filePath}:{lineNumber}, input: {input}", ex); + return Signal.Return(defaultValue); + }); } } diff --git a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs index 521ae84c..4931dd23 100644 --- a/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs +++ b/v2rayN/ServiceLib/Models/Configs/ConfigItems.cs @@ -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 ConnectionsColumnItem { get; set; } diff --git a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs index c9f47ec2..3fca12dc 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashConnectionsViewModel.cs @@ -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 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); } } } diff --git a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs index cc76214b..ea2bdbc1 100644 --- a/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/ClashProxiesViewModel.cs @@ -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 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); } } diff --git a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs index 21c3049b..5f244766 100644 --- a/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs +++ b/v2rayN/ServiceLib/ViewModels/MsgViewModel.cs @@ -2,12 +2,11 @@ namespace ServiceLib.ViewModels; public partial class MsgViewModel : MyReactiveObject { - public Interaction DispatcherShowMsgInteraction { get; } = new(); + public Interaction ShowMsgInteraction { get; } = new(); private readonly ConcurrentQueue _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) diff --git a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs index d7bc486c..9e92fa83 100644 --- a/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs +++ b/v2rayN/v2rayN.Desktop/Views/MsgView.axaml.cs @@ -16,15 +16,12 @@ public partial class MsgView : ReactiveUserControl 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( diff --git a/v2rayN/v2rayN/Views/MsgView.xaml.cs b/v2rayN/v2rayN/Views/MsgView.xaml.cs index b05f8cd0..6ba16774 100644 --- a/v2rayN/v2rayN/Views/MsgView.xaml.cs +++ b/v2rayN/v2rayN/Views/MsgView.xaml.cs @@ -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;