diff --git a/WinSyncScroll/ViewModels/MainViewModel.cs b/WinSyncScroll/ViewModels/MainViewModel.cs index 0ca4fab..63fcc9d 100644 --- a/WinSyncScroll/ViewModels/MainViewModel.cs +++ b/WinSyncScroll/ViewModels/MainViewModel.cs @@ -545,6 +545,38 @@ public void HandleWindowClosing() } } + private void WaitForTaskCompletion(Task? task, string taskName, TimeSpan timeout) + { +#pragma warning disable VSTHRD002 // Synchronously waiting is acceptable in shutdown scenarios + try + { + if (task is not null + && !task.Wait(timeout)) + { + _logger.LogWarning("{TaskName} did not complete within timeout", taskName); + } + } + catch (AggregateException ae) + { + ae.Handle(ex => + { + if (ex is OperationCanceledException) + { + _logger.LogDebug("{TaskName} was cancelled", taskName); + return true; + } + + _logger.LogError(ex, "Error waiting for {TaskName} to complete", taskName); + return true; + }); + } + catch (Exception e) + { + _logger.LogError(e, "Error waiting for {TaskName} to complete", taskName); + } +#pragma warning restore VSTHRD002 + } + public void Dispose() { AppState = AppState.NotRunning; @@ -557,8 +589,16 @@ public void Dispose() _logger.LogError(e, "Error cancelling the cancellation token source"); } + // Wait for tasks to complete before disposing resources + // Using Task.Wait in Dispose is acceptable as this is a shutdown scenario + // and we need to ensure tasks complete before disposing underlying resources + WaitForTaskCompletion(_updateMouseHookRectsLoopTask, "Update mouse hook rects loop task", TimeSpan.FromSeconds(5)); + WaitForTaskCompletion(_mouseEventProcessingLoopTask, "Mouse event processing loop task", TimeSpan.FromSeconds(5)); + + // Dispose tasks after they have completed _updateMouseHookRectsLoopTask?.Dispose(); _mouseEventProcessingLoopTask?.Dispose(); + _mouseHook.Dispose(); _cancellationTokenSource.Dispose(); }