diff --git a/SecRandom/App.axaml.cs b/SecRandom/App.axaml.cs index 9d3aba70..7b9da8f0 100644 --- a/SecRandom/App.axaml.cs +++ b/SecRandom/App.axaml.cs @@ -878,7 +878,11 @@ private void BuildHost(IPlatformServiceRoot platform) services.AddSingleton(); services.AddSingleton(serviceProvider => serviceProvider.GetRequiredService()); - services.AddSingleton(); + services.AddSingleton(serviceProvider => + new NotificationService( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService>(), + serviceProvider.GetRequiredService())); services.AddSingleton(serviceProvider => new MusicLibraryService( serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>(), @@ -888,6 +892,7 @@ private void BuildHost(IPlatformServiceRoot platform) services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); diff --git a/SecRandom/Services/Linkage/ClassIslandIpcConnection.cs b/SecRandom/Services/Linkage/ClassIslandIpcConnection.cs new file mode 100644 index 00000000..d7895e2a --- /dev/null +++ b/SecRandom/Services/Linkage/ClassIslandIpcConnection.cs @@ -0,0 +1,303 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using ClassIsland.Shared.IPC; +using ClassIsland.Shared.IPC.Abstractions.Services; +using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; +using dotnetCampus.Ipc.Pipes; +using Microsoft.Extensions.Logging; +using SecRandom4Ci.Interface.Services; + +namespace SecRandom.Services.Linkage; + +public sealed class ClassIslandIpcConnection : IDisposable +{ + private static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(5); + private static readonly TimeSpan JsonRouteReadyDelay = TimeSpan.FromSeconds(1); + private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(5); + private static readonly TimeSpan MinRetryDelay = TimeSpan.FromSeconds(5); + private static readonly TimeSpan MaxRetryDelay = TimeSpan.FromMinutes(5); + private static readonly Version MinimumPluginVersion = new(1, 2, 0, 0); + + private readonly ILogger _logger; + private readonly SemaphoreSlim _connectionGate = new(1, 1); + private readonly object _stateLock = new(); + + private IpcClient? _client; + private IPublicLessonsService? _lessonsService; + private ISecRandomService? _notificationService; + private DateTimeOffset _nextConnectAttempt = DateTimeOffset.MinValue; + private TimeSpan _currentRetryDelay = MinRetryDelay; + private bool _isDisposed; + private volatile int _connectionState; // 0=disconnected, 1=connecting, 2=connected + + public IPublicLessonsService? LessonsService + { + get + { + lock (_stateLock) + { + return _lessonsService; + } + } + } + + public ISecRandomService? NotificationService + { + get + { + lock (_stateLock) + { + return _notificationService; + } + } + } + + public bool IsConnected + { + get + { + lock (_stateLock) + { + return _lessonsService is not null; + } + } + } + + public event EventHandler? StateChanged; + + public ClassIslandIpcConnection(ILogger logger) + { + _logger = logger; + } + + public async Task GetLessonsServiceAsync(CancellationToken cancellationToken = default) + { + var service = LessonsService; + if (service is not null) + return service; + + // Trigger connection if not already trying + if (Interlocked.CompareExchange(ref _connectionState, 1, 0) == 0) + { + _ = Task.Run(() => TryConnectAsync(cancellationToken)); + } + + // Wait for connection with timeout + var timeout = TimeSpan.FromSeconds(10); + var start = DateTime.UtcNow; + while (DateTime.UtcNow - start < timeout) + { + service = LessonsService; + if (service is not null) + return service; + await Task.Delay(100, cancellationToken).ConfigureAwait(false); + } + return null; + } + + public async Task GetNotificationServiceAsync(CancellationToken cancellationToken = default) + { + var service = NotificationService; + if (service is not null) + return service; + + // Ensure connection is attempted + await GetLessonsServiceAsync(cancellationToken).ConfigureAwait(false); + return NotificationService; + } + + private async Task TryConnectAsync(CancellationToken cancellationToken) + { + try + { + await EnsureConnectedAsync(cancellationToken).ConfigureAwait(false); + } + finally + { + Interlocked.Exchange(ref _connectionState, 0); + } + } + + private async Task EnsureConnectedAsync(CancellationToken cancellationToken) + { + if (DateTimeOffset.UtcNow < _nextConnectAttempt) + return false; + + await _connectionGate.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + if (IsConnected) + return true; + + if (DateTimeOffset.UtcNow < _nextConnectAttempt) + return false; + + var client = new IpcClient(); + + // Subscribe to ClassIsland lifecycle notifications (NOT CurrentTimeStateChanged which fires every second) + client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnClassNotifyId, OnClassIslandStateChanged); + client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnBreakingTimeNotifyId, OnClassIslandStateChanged); + client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnAfterSchoolNotifyId, OnClassIslandStateChanged); + + try + { + await client.Connect().WaitAsync(ConnectTimeout, cancellationToken).ConfigureAwait(false); + await Task.Delay(JsonRouteReadyDelay, cancellationToken).ConfigureAwait(false); + + if (client.PeerProxy is null) + { + DisposeClient(client); + ScheduleRetry(); + return false; + } + + // Handle connection broken - on the PEER, not the provider + client.PeerProxy!.PeerConnectionBroken += (_, _) => OnPeerConnectionBroken(); + + var lessons = GeneratedIpcFactory.CreateIpcProxy(client.Provider, client.PeerProxy); + + // Test if lessons service works (ClassIsland core IPC) + bool lessonsWork = false; + try + { + _ = lessons.IsTimerRunning; + lessonsWork = true; + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Lessons service test failed: {ex.Message}"); + } + + // Try to get notification service (SecRandom4Ci plugin - optional) + ISecRandomService? notification = null; + try + { + notification = client.Provider.CreateIpcProxy(client.PeerProxy); + var isAlive = notification.IsAlive(); + if (!string.Equals(isAlive, "Yes", StringComparison.Ordinal)) + { + notification = null; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"Notification service not available: {ex.Message}"); + notification = null; + } + + if (!lessonsWork) + { + _logger.LogDebug("ClassIsland IPC 连接成功但课程服务不可用。"); + DisposeClient(client); + ScheduleRetry(); + return false; + } + + _client = client; + _lessonsService = lessons; + _notificationService = notification; + _nextConnectAttempt = DateTimeOffset.MinValue; + _currentRetryDelay = MinRetryDelay; + + if (notification is not null) + { + _logger.LogInformation("已连接到 ClassIsland IPC:管道={PipeName},SecRandom4Ci 插件可用。", IpcClient.PipeName); + } + else + { + _logger.LogInformation("已连接到 ClassIsland IPC:管道={PipeName},仅课程联动可用(未安装 SecRandom4Ci 插件)。", IpcClient.PipeName); + } + + // Notify state changed on successful connection + StateChanged?.Invoke(this, EventArgs.Empty); + return true; + } + catch (Exception exception) + { + _logger.LogDebug(exception, "连接 ClassIsland IPC 失败,将在 {RetryDelay} 后重试。", _currentRetryDelay); + DisposeClient(client); + ScheduleRetry(); + return false; + } + } + finally + { + _connectionGate.Release(); + } + } + + private void OnPeerConnectionBroken() + { + if (_isDisposed) + return; + + _logger.LogDebug("ClassIsland IPC 连接已断开,将尝试重连。"); + InvalidateConnection(); + // 重置重试时间,允许立即重连(500ms 后) + _nextConnectAttempt = DateTimeOffset.MinValue; + _currentRetryDelay = MinRetryDelay; + + // Delay reconnection to avoid race with ClassIsland's broadcast loop + // ClassIsland broadcasts currentTimeStateChanged every second; + // immediate reconnect can race with its BroadcastNotificationAsync + _ = Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(500)).ConfigureAwait(false); + if (!_isDisposed) + await TryConnectAsync(CancellationToken.None).ConfigureAwait(false); + }); + + // Notify state changed on disconnection + StateChanged?.Invoke(this, EventArgs.Empty); + } + + private void OnClassIslandStateChanged() + { + if (_isDisposed) + return; + + StateChanged?.Invoke(this, EventArgs.Empty); + } + + private void InvalidateConnection() + { + lock (_stateLock) + { + _lessonsService = null; + _notificationService = null; + } + DisposeClient(_client); + _client = null; + } + + private static void DisposeClient(IpcClient? client) + { + if (client is null) + return; + + try + { + client.Provider.Dispose(); + } + catch (Exception) + { + } + } + + private void ScheduleRetry() + { + _nextConnectAttempt = DateTimeOffset.UtcNow.Add(_currentRetryDelay); + _currentRetryDelay = TimeSpan.FromSeconds(Math.Min(_currentRetryDelay.TotalSeconds * 2, MaxRetryDelay.TotalSeconds)); + } + + public void Dispose() + { + if (_isDisposed) + return; + + _isDisposed = true; + InvalidateConnection(); + _connectionGate.Dispose(); + } +} \ No newline at end of file diff --git a/SecRandom/Services/Linkage/ClassIslandScheduleSource.cs b/SecRandom/Services/Linkage/ClassIslandScheduleSource.cs index 8643ba50..48934384 100644 --- a/SecRandom/Services/Linkage/ClassIslandScheduleSource.cs +++ b/SecRandom/Services/Linkage/ClassIslandScheduleSource.cs @@ -4,31 +4,31 @@ using ClassIsland.Shared.Enums; using ClassIsland.Shared.IPC; using ClassIsland.Shared.IPC.Abstractions.Services; -using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; -using dotnetCampus.Ipc.Pipes; using Microsoft.Extensions.Logging; using SecRandom.Core.Models.Linkage; namespace SecRandom.Services.Linkage; -public sealed class ClassIslandScheduleSource(ILogger logger) : ICourseScheduleSource +public sealed class ClassIslandScheduleSource : ICourseScheduleSource { - private static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(5); - private static readonly TimeSpan JsonRouteReadyDelay = TimeSpan.FromSeconds(1); - private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(5); - private readonly SemaphoreSlim _connectionGate = new(1, 1); - private IpcClient? _client; - private IPublicLessonsService? _lessons; + private readonly ClassIslandIpcConnection _ipcConnection; + private readonly ILogger _logger; private string _lastKnownCourseName = string.Empty; private DateOnly? _lastKnownCourseDate; - private DateTimeOffset _nextConnectAttempt = DateTimeOffset.MinValue; public string SourceName => "ClassIsland"; public event EventHandler? StateChanged; + public ClassIslandScheduleSource(ClassIslandIpcConnection ipcConnection, ILogger logger) + { + _ipcConnection = ipcConnection; + _logger = logger; + _ipcConnection.StateChanged += (_, _) => StateChanged?.Invoke(this, EventArgs.Empty); + } + public async Task GetSnapshotAsync(CancellationToken cancellationToken = default) { - var lessons = await GetLessonsAsync(cancellationToken).ConfigureAwait(false); + var lessons = await _ipcConnection.GetLessonsServiceAsync(cancellationToken).ConfigureAwait(false); if (lessons is null) return CourseScheduleSnapshot.Unavailable(SourceName, ScheduleErrorCodes.ClassIslandUnavailable); @@ -85,6 +85,8 @@ public async Task GetSnapshotAsync(CancellationToken can var currentCourseRemaining = state == CourseTimeState.OnClass ? Positive(lessons.OnBreakingTimeLeftTime) : null; + // Version only includes stable identifiers (schedule index + state), NOT countdown timers + // This prevents false StateChanged triggers from continuously changing OnClassLeftTime/OnBreakingTimeLeftTime return new CourseScheduleSnapshot( true, state, @@ -99,89 +101,11 @@ public async Task GetSnapshotAsync(CancellationToken can } catch (Exception exception) { - logger.LogDebug(exception, "读取 ClassIsland 日程状态失败。"); - InvalidateConnection(); + _logger.LogDebug(exception, "读取 ClassIsland 日程状态失败。"); return CourseScheduleSnapshot.Unavailable(SourceName, ScheduleErrorCodes.ClassIslandReadFailed); } } - private async Task GetLessonsAsync(CancellationToken cancellationToken) - { - if (_lessons is not null) - return _lessons; - if (DateTimeOffset.UtcNow < _nextConnectAttempt) - return null; - - await _connectionGate.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - if (_lessons is not null) - return _lessons; - if (DateTimeOffset.UtcNow < _nextConnectAttempt) - return null; - - var client = new IpcClient(); - client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnClassNotifyId, OnClassIslandStateChanged); - client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnBreakingTimeNotifyId, OnClassIslandStateChanged); - client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.OnAfterSchoolNotifyId, OnClassIslandStateChanged); - client.JsonIpcProvider.AddNotifyHandler(IpcRoutedNotifyIds.CurrentTimeStateChangedNotifyId, OnClassIslandStateChanged); - await client.Connect().WaitAsync(ConnectTimeout, cancellationToken).ConfigureAwait(false); - // ClassIsland establishes its JSON routed peer asynchronously after the transport connection. - await Task.Delay(JsonRouteReadyDelay, cancellationToken).ConfigureAwait(false); - if (client.PeerProxy is null) - { - DisposeClient(client); - ScheduleRetry(); - return null; - } - - _client = client; - _lessons = GeneratedIpcFactory.CreateIpcProxy(client.Provider, client.PeerProxy); - _nextConnectAttempt = DateTimeOffset.MinValue; - logger.LogInformation("已连接到 ClassIsland IPC:管道={PipeName}。", IpcClient.PipeName); - return _lessons; - } - catch (Exception exception) - { - logger.LogDebug(exception, "连接 ClassIsland IPC 失败,将在 {RetryDelay} 后重试。", RetryDelay); - InvalidateConnection(); - ScheduleRetry(); - return null; - } - finally - { - _connectionGate.Release(); - } - } - - private void OnClassIslandStateChanged() - { - StateChanged?.Invoke(this, EventArgs.Empty); - } - - private void InvalidateConnection() - { - _lessons = null; - DisposeClient(_client); - _client = null; - } - - private static void DisposeClient(IpcClient? client) - { - try - { - client?.Provider.Dispose(); - } - catch (Exception) - { - } - } - - private void ScheduleRetry() - { - _nextConnectAttempt = DateTimeOffset.UtcNow.Add(RetryDelay); - } - private static string NormalizeSubjectName(string? name) { var normalized = name?.Trim() ?? string.Empty; diff --git a/SecRandom/Services/Linkage/CourseLinkageService.cs b/SecRandom/Services/Linkage/CourseLinkageService.cs index f1a69030..ee5b1fd5 100644 --- a/SecRandom/Services/Linkage/CourseLinkageService.cs +++ b/SecRandom/Services/Linkage/CourseLinkageService.cs @@ -58,8 +58,11 @@ public async Task RefreshAsync(CancellationToken cancellationToken = default) var next = source is null ? CourseScheduleSnapshot.Unavailable("Off") : await source.GetSnapshotAsync(cancellationToken).ConfigureAwait(false); - if (Equals(_snapshot, next)) + + // Semantic comparison: ignore time-varying fields (countdown timers) + if (SnapshotsEqual(_snapshot, next)) return; + _snapshot = next; stateChanged = true; } @@ -71,7 +74,7 @@ public async Task RefreshAsync(CancellationToken cancellationToken = default) { _logger.LogWarning(exception, "刷新课程联动状态失败。"); var unavailable = CourseScheduleSnapshot.Unavailable("Unknown", exception.Message); - if (!Equals(_snapshot, unavailable)) + if (!SnapshotsEqual(_snapshot, unavailable)) { _snapshot = unavailable; stateChanged = true; @@ -86,6 +89,18 @@ public async Task RefreshAsync(CancellationToken cancellationToken = default) NotifyStateChanged(); } + private static bool SnapshotsEqual(CourseScheduleSnapshot a, CourseScheduleSnapshot b) + { + return a.IsAvailable == b.IsAvailable + && a.State == b.State + && a.Source == b.Source + && a.Version == b.Version + && a.CurrentCourse?.Name == b.CurrentCourse?.Name + && a.PreviousCourse?.Name == b.PreviousCourse?.Name + && a.NextCourse?.Name == b.NextCourse?.Name + && a.Error == b.Error; + } + public bool IsConfirmedBreakTime => _snapshot.IsAvailable && _snapshot.State == CourseTimeState.Breaking && !IsWithinEnableWindow(_snapshot); @@ -126,8 +141,9 @@ public string GetSubjectFilter() public TimeSpan GetNextRefreshDelay() { + // Only poll when completely unavailable - otherwise rely on events if (Settings.DataSource == LinkageDataSource.ClassIsland && !_snapshot.IsAvailable) - return TimeSpan.FromSeconds(5); + return TimeSpan.FromMinutes(5); List candidates = []; if (_snapshot.TimeUntilNextCourse is { } untilNext && untilNext > TimeSpan.Zero) diff --git a/SecRandom/Services/Notification/NotificationService.cs b/SecRandom/Services/Notification/NotificationService.cs index 32b34b2f..884dcbd4 100644 --- a/SecRandom/Services/Notification/NotificationService.cs +++ b/SecRandom/Services/Notification/NotificationService.cs @@ -1,11 +1,11 @@ using Avalonia.Threading; using ClassIsland.Shared.IPC; -using dotnetCampus.Ipc.CompilerServices.GeneratedProxies; using Microsoft.Extensions.Logging; using SecRandom.Core.Enums; using SecRandom.Core.Models.SubConfigs; using SecRandom.Core.Services.Config; using SecRandom.Core.Services.Draw; +using SecRandom.Services.Linkage; using SecRandom.Shared.Models.Profile; using SecRandom4Ci.Interface.Enums; using SecRandom4Ci.Interface.Models; @@ -16,26 +16,22 @@ namespace SecRandom.Services.Notification; public sealed class NotificationService : IDisposable { - private static readonly Version MinimumPluginVersion = new(1, 2, 0, 0); - private static readonly TimeSpan ConnectTimeout = TimeSpan.FromSeconds(1); private static readonly TimeSpan InvocationTimeout = TimeSpan.FromSeconds(1); - private static readonly TimeSpan JsonRouteReadyDelay = TimeSpan.FromMilliseconds(100); private static readonly TimeSpan RetryDelay = TimeSpan.FromSeconds(5); private readonly MainConfigHandler _configHandler; private readonly ILogger _logger; + private readonly ClassIslandIpcConnection _ipcConnection; private readonly CryptoRandomSource _previewRandom = new(); private readonly SemaphoreSlim _sendGate = new(1, 1); - private IpcClient? _classIslandClient; - private ISecRandomService? _classIslandService; - private DateTimeOffset _nextClassIslandConnectAttempt = DateTimeOffset.MinValue; private bool _quickDrawBuiltInPreviewActive; private bool _isDisposed; - public NotificationService(MainConfigHandler configHandler, ILogger logger) + public NotificationService(MainConfigHandler configHandler, ILogger logger, ClassIslandIpcConnection ipcConnection) { _configHandler = configHandler; _logger = logger; + _ipcConnection = ipcConnection; } public void QueueStudents( @@ -270,90 +266,61 @@ private async Task SendToClassIslandAsync(NotificationData notification, Action? if (_isDisposed) return; - var service = await GetClassIslandServiceAsync().ConfigureAwait(false); + var service = await _ipcConnection.GetNotificationServiceAsync().ConfigureAwait(false); if (service is null) { builtInFallback?.Invoke(); return; } - if (!string.Equals( - await InvokeClassIslandAsync(service.IsAlive).ConfigureAwait(false), - "Yes", - StringComparison.Ordinal)) + string? isAlive = null; + try + { + isAlive = service.IsAlive(); + } + catch (AggregateException aggEx) when (aggEx.InnerExceptions.Count == 1) + { + System.Diagnostics.Debug.WriteLine($"IPC notification IsAlive failed: {aggEx.InnerExceptions[0].Message}"); + } + catch (dotnetCampus.Ipc.Exceptions.IpcPeerConnectionBrokenException) + { + // Peer disconnected during IsAlive check + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"IPC notification IsAlive failed: {ex.Message}"); + } + + if (!string.Equals(isAlive, "Yes", StringComparison.Ordinal)) { _logger.LogDebug("SecRandom4Ci 通知服务未响应。"); - InvalidateClassIslandConnection(); - ScheduleClassIslandRetry(); builtInFallback?.Invoke(); return; } - await InvokeClassIslandAsync(() => service.ShowNotification(notification)).ConfigureAwait(false); - } - catch (Exception exception) - { - _logger.LogDebug(exception, "通过 SecRandom4Ci 插件发送 ClassIsland 通知失败。"); - InvalidateClassIslandConnection(); - ScheduleClassIslandRetry(); - builtInFallback?.Invoke(); - } - finally - { - _sendGate.Release(); - } - } - - private async Task GetClassIslandServiceAsync() - { - if (_isDisposed) - return null; - if (_classIslandService is not null) - return _classIslandService; - if (DateTimeOffset.UtcNow < _nextClassIslandConnectAttempt) - return null; - - IpcClient? client = null; - try - { - client = new IpcClient(); - await client.Connect().WaitAsync(ConnectTimeout).ConfigureAwait(false); - // ClassIsland initializes the JSON routed peer after the named pipe connects. - await Task.Delay(JsonRouteReadyDelay).ConfigureAwait(false); - if (client.PeerProxy is null) + try { - DisposeClient(client); - ScheduleClassIslandRetry(); - return null; + service.ShowNotification(notification); } - - var service = client.Provider.CreateIpcProxy(client.PeerProxy); - var pluginVersion = await InvokeClassIslandAsync(service.GetPluginVersion).ConfigureAwait(false); - if (pluginVersion is null || pluginVersion < MinimumPluginVersion || - !string.Equals( - await InvokeClassIslandAsync(service.IsAlive).ConfigureAwait(false), - "Yes", - StringComparison.Ordinal)) + catch (AggregateException aggEx) when (aggEx.InnerExceptions.Count == 1) { - _logger.LogDebug("SecRandom4Ci 插件不可用或版本低于 {MinimumPluginVersion}。", MinimumPluginVersion); - DisposeClient(client); - ScheduleClassIslandRetry(); - return null; + System.Diagnostics.Debug.WriteLine($"IPC notification ShowNotification failed: {aggEx.InnerExceptions[0].Message}"); + builtInFallback?.Invoke(); + } + catch (dotnetCampus.Ipc.Exceptions.IpcPeerConnectionBrokenException) + { + // Peer disconnected during notification - trigger fallback silently + builtInFallback?.Invoke(); + } + catch (Exception ex) + { + _logger.LogDebug(ex, "通过 SecRandom4Ci 插件发送 ClassIsland 通知失败。"); + builtInFallback?.Invoke(); } - - _classIslandClient = client; - _classIslandService = service; - _nextClassIslandConnectAttempt = DateTimeOffset.MinValue; - _logger.LogInformation("已连接到 ClassIsland IPC,SecRandom4Ci {PluginVersion} 通知服务可用。", pluginVersion); - return _classIslandService; } - catch (Exception exception) + finally { - _logger.LogDebug(exception, "连接 ClassIsland IPC 的 SecRandom4Ci 通知服务失败。"); - DisposeClient(client); - InvalidateClassIslandConnection(); - ScheduleClassIslandRetry(); - return null; + _sendGate.Release(); } } @@ -396,42 +363,6 @@ private List DrawPreviewItems(List candidates, int drawCount) return candidates.Take(count).ToList(); } - private void InvalidateClassIslandConnection() - { - _classIslandService = null; - DisposeClient(_classIslandClient); - _classIslandClient = null; - } - - private static void DisposeClient(IpcClient? client) - { - if (client is null) - return; - - try - { - client.Provider.Dispose(); - } - catch (Exception) - { - } - } - - private static Task InvokeClassIslandAsync(Func invoke) - { - return Task.Run(invoke).WaitAsync(InvocationTimeout); - } - - private static Task InvokeClassIslandAsync(Action invoke) - { - return Task.Run(invoke).WaitAsync(InvocationTimeout); - } - - private void ScheduleClassIslandRetry() - { - _nextClassIslandConnectAttempt = DateTimeOffset.UtcNow.Add(RetryDelay); - } - private static ResultType GetResultType(NotificationSettingsType type) { return type switch @@ -467,6 +398,5 @@ private static string DisplayValue(string primary, string fallback) public void Dispose() { _isDisposed = true; - InvalidateClassIslandConnection(); } }