From 92ecd2e93dde8e535d79b5a9afcff469b3bba8f1 Mon Sep 17 00:00:00 2001 From: Stone_Red <56473591+Stone-Red-Code@users.noreply.github.com> Date: Tue, 13 Jan 2026 22:42:15 +0100 Subject: [PATCH] Add cancellation support to AsyncPlugin methods --- .../BuiltInPlugins/WeatherPlugin.cs | 5 ++-- src/DesktopMagic/Plugins/PluginWindow.xaml.cs | 28 ++++++++++++++++++- src/DesktopMagicPluginAPI/AsyncPlugin.cs | 15 +++++++++- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs b/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs index 45f52e9..722cd2c 100644 --- a/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs +++ b/src/DesktopMagic/BuiltInPlugins/WeatherPlugin.cs @@ -7,6 +7,7 @@ using System.Drawing; using System.Globalization; using System.Net.Http; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; namespace DesktopMagic.BuiltInPlugins; @@ -46,7 +47,7 @@ public class WeatherPlugin : AsyncPlugin private string lastUpdated = ""; private bool isLoading = false; - public override async void Start() + public override async Task StartAsync(CancellationToken cancellationToken) { httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Mozilla/5.0 (DesktopMagic)"); @@ -74,7 +75,7 @@ public class WeatherPlugin : AsyncPlugin }; } - public override async Task MainAsync() + public override async Task MainAsync(CancellationToken cancellationToken) { // Periodic background update if (!isLoading && cachedLat != null && cachedLon != null) diff --git a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs index 3942518..6059d53 100644 --- a/src/DesktopMagic/Plugins/PluginWindow.xaml.cs +++ b/src/DesktopMagic/Plugins/PluginWindow.xaml.cs @@ -37,6 +37,8 @@ public partial class PluginWindow : Window, IPluginWindow private Plugin? pluginClassInstance; private readonly AssemblyLoadContext assemblyLoadContext; + private CancellationTokenSource? pluginCancellationTokenSource; + public bool IsRunning { get; private set; } = true; public PluginMetadata PluginMetadata { get; private set; } public string PluginFolderPath { get; private set; } @@ -315,6 +317,12 @@ public partial class PluginWindow : Window, IPluginWindow updateTimer.Elapsed += UpdateTimer_Elapsed; pluginClassInstance.Start(); + if (pluginClassInstance is AsyncPlugin asyncPluginStart) + { + pluginCancellationTokenSource?.Dispose(); + pluginCancellationTokenSource = new CancellationTokenSource(); + await asyncPluginStart.StartAsync(pluginCancellationTokenSource.Token); + } UpdatePluginWindow(); await Dispatcher.InvokeAsync(ThemeChanged); @@ -602,7 +610,8 @@ public partial class PluginWindow : Window, IPluginWindow if (pluginClassInstance is AsyncPlugin asyncPlugin) { - result = await asyncPlugin.MainAsync(); + CancellationToken token = pluginCancellationTokenSource?.Token ?? CancellationToken.None; + result = await asyncPlugin.MainAsync(token); } else { @@ -678,6 +687,20 @@ public partial class PluginWindow : Window, IPluginWindow try { + pluginCancellationTokenSource?.Cancel(); + + if (pluginClassInstance is AsyncPlugin asyncPluginStop) + { + try + { + _ = asyncPluginStop.StopAsync(pluginCancellationTokenSource?.Token ?? CancellationToken.None); + } + catch (Exception ex) + { + App.Logger.LogError($"\"{PluginMetadata.Name}\" - {ex}", source: "Plugin"); + } + } + if (pluginClassInstance is not null) { SaveState(pluginClassInstance); @@ -690,6 +713,9 @@ public partial class PluginWindow : Window, IPluginWindow } assemblyLoadContext.Unload(); + + pluginCancellationTokenSource?.Dispose(); + pluginCancellationTokenSource = null; } #region Window Events diff --git a/src/DesktopMagicPluginAPI/AsyncPlugin.cs b/src/DesktopMagicPluginAPI/AsyncPlugin.cs index ecb90f2..70db345 100644 --- a/src/DesktopMagicPluginAPI/AsyncPlugin.cs +++ b/src/DesktopMagicPluginAPI/AsyncPlugin.cs @@ -1,5 +1,6 @@ using System; using System.Drawing; +using System.Threading; using System.Threading.Tasks; namespace DesktopMagic.Api; @@ -15,11 +16,23 @@ namespace DesktopMagic.Api; /// intervals, and configuration changes as needed. public abstract class AsyncPlugin : Plugin { + /// + /// Occurs once when the plugin gets activated. Override for async initialization. + /// + /// Token signaled when the host requests cancellation. + public virtual Task StartAsync(CancellationToken cancellationToken) => Task.CompletedTask; + + /// + /// Occurs once when the plugin gets deactivated. Override for async cleanup. + /// + /// Token signaled when the host requests cancellation. + public virtual Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; + /// /// Occurs when the elapses. /// /// - public abstract Task MainAsync(); + public abstract Task MainAsync(CancellationToken cancellationToken); /// /// This method should not be called directly! Override and use for asynchronous plugin operations.