From 625f3ee42c2cd0802a69658801da70645aa9e00c Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 18 Aug 2022 20:59:07 +0800 Subject: [PATCH] switch to main thread async --- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 57 ++++++++++--------- .../SynchronizationContextAwaitable.cs | 30 ++++++++++ .../SynchronizationContextAwaiter.cs | 43 ++++++++++++++ .../Snap.Hutao/Core/Threading/Watcher.cs | 2 +- .../Snap.Hutao/Core/Validation/Must.cs | 14 +++++ .../Snap.Hutao/Core/WebView2Helper.cs | 3 +- src/Snap.Hutao/Snap.Hutao/Program.cs | 13 ++++- .../Snap.Hutao/Service/InfoBarService.cs | 22 +++---- 8 files changed, 145 insertions(+), 39 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaitable.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaiter.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index 31f81298..abbbc6a3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -1,7 +1,6 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using CommunityToolkit.WinUI; using Microsoft.Windows.AppLifecycle; using Snap.Hutao.Extension; using Snap.Hutao.Service.Abstraction; @@ -14,11 +13,12 @@ namespace Snap.Hutao.Core.LifeCycle; /// internal static class Activation { - public static volatile bool isActivating = false; - public static object activationLock = new object(); + private static volatile bool isActivating = false; + private static object activationLock = new(); /// /// 响应激活事件 + /// 激活事件一般不会在UI线程上触发 /// /// 发送方 /// 激活参数 @@ -49,19 +49,7 @@ internal static class Activation isActivating = true; } - App.Window = Ioc.Default.GetRequiredService(); - - IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); - await infoBarService.WaitInitializationAsync(); - - if (args.Kind == ExtendedActivationKind.Protocol) - { - if (args.TryGetProtocolActivatedUri(out Uri? uri)) - { - infoBarService.Information(uri.ToString()); - await HandleUrlActivationAsync(uri); - } - } + await HandleActivationCoreAsync(args).ConfigureAwait(false); lock (activationLock) { @@ -69,20 +57,37 @@ internal static class Activation } } + private static async Task HandleActivationCoreAsync(AppActivationArguments args) + { + App.Window = Ioc.Default.GetRequiredService(); + + IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); + await infoBarService.WaitInitializationAsync().ConfigureAwait(false); + + if (args.Kind == ExtendedActivationKind.Protocol) + { + if (args.TryGetProtocolActivatedUri(out Uri? uri)) + { + infoBarService.Information(uri.ToString()); + await HandleUrlActivationAsync(uri).ConfigureAwait(false); + } + } + } + private static async Task HandleUrlActivationAsync(Uri uri) { UriBuilder builder = new(uri); - Requires.Argument(builder.Scheme == "hutao", nameof(uri), "uri 的协议不正确"); + Must.Argument(builder.Scheme == "hutao", "uri 的协议不正确"); string category = builder.Host.ToLowerInvariant(); string action = builder.Path.ToLowerInvariant(); - string rawParameter = builder.Query; + string rawParameter = builder.Query.ToLowerInvariant(); switch (category) { case "achievement": { - await HandleAchievementActionAsync(action, rawParameter); + await HandleAchievementActionAsync(action, rawParameter).ConfigureAwait(false); break; } } @@ -94,13 +99,13 @@ internal static class Activation { case "/import": { - await Program.UIDispatcherQueue.EnqueueAsync(async () => - { - INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri"); - await Ioc.Default - .GetRequiredService() - .NavigateAsync(navigationAwaiter, true); - }); + await Program.SwitchToMainThreadAsync(); + + INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri"); + await Ioc.Default + .GetRequiredService() + .NavigateAsync(navigationAwaiter, true) + .ConfigureAwait(false); break; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaitable.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaitable.cs new file mode 100644 index 00000000..d131549e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaitable.cs @@ -0,0 +1,30 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Threading; + +/// +/// 同步上下文等待体 +/// +public struct SynchronizationContextAwaitable +{ + private readonly SynchronizationContext context; + + /// + /// 构造一个新的同步上下文等待体 + /// + /// 同步上下文 + public SynchronizationContextAwaitable(SynchronizationContext context) + { + this.context = context; + } + + /// + /// 获取等待器 + /// + /// 等待器 + public SynchronizationContextAwaiter GetAwaiter() + { + return new SynchronizationContextAwaiter(context); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaiter.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaiter.cs new file mode 100644 index 00000000..e735fbcd --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/SynchronizationContextAwaiter.cs @@ -0,0 +1,43 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.CompilerServices; + +namespace Snap.Hutao.Core.Threading; + +/// +/// 同步上下文等待器 +/// +public struct SynchronizationContextAwaiter : INotifyCompletion +{ + private static readonly SendOrPostCallback PostCallback = state => ((Action)state!)(); + private readonly SynchronizationContext context; + + /// + /// 构造一个新的同步上下文等待器 + /// + /// 同步上下文 + public SynchronizationContextAwaiter(SynchronizationContext context) + { + this.context = context; + } + + /// + /// 是否完成 + /// + public bool IsCompleted => context == SynchronizationContext.Current; + + /// + /// 完成操作 + /// + /// 后续操作 + [SuppressMessage("", "VSTHRD001")] + public void OnCompleted(Action continuation) => context.Post(PostCallback, continuation); + + /// + /// 获取执行结果 + /// + public void GetResult() + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs index 0cf7c88c..70822137 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/Watcher.cs @@ -77,4 +77,4 @@ public class Watcher : Observable watcher.IsCompleted = true; } } -} \ No newline at end of file +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs index 9c79c058..6189def4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Validation/Must.cs @@ -10,6 +10,20 @@ namespace Snap.Hutao.Core.Validation; /// public static class Must { + /// + /// Throws an if a condition does not evaluate to true. + /// + /// The condition to check. + /// message + /// The name of the parameter to blame in the exception, if thrown. + public static void Argument([DoesNotReturnIf(false)] bool condition, string? message, [CallerArgumentExpression("condition")] string? parameterName = null) + { + if (!condition) + { + throw new ArgumentException(message, parameterName); + } + } + /// /// Unconditionally throws an . /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs index 32aeac91..9cea7ea8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/WebView2Helper.cs @@ -10,8 +10,9 @@ namespace Snap.Hutao.Core; /// /// 检测 WebView2运行时 是否存在 /// 不再使用注册表检查方式 +/// 必须为抽象类才能使用泛型日志器 /// -internal static class WebView2Helper +internal abstract class WebView2Helper { private static bool hasEverDetected = false; private static bool isSupported = false; diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index 818c9fd0..32e63b58 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Threading; using System.Runtime.InteropServices; using WinRT; @@ -14,6 +15,7 @@ namespace Snap.Hutao; public static class Program { private static volatile DispatcherQueue? dispatcherQueue; + private static volatile SynchronizationContext? context; /// /// 主线程调度器队列 @@ -23,6 +25,15 @@ public static class Program get => Must.NotNull(dispatcherQueue!); } + /// + /// 异步切换到主线程 + /// + /// 等待体 + public static SynchronizationContextAwaitable SwitchToMainThreadAsync() + { + return new SynchronizationContextAwaitable(context!); + } + [DllImport("Microsoft.ui.xaml.dll")] private static extern void XamlCheckProcessRequirements(); @@ -36,7 +47,7 @@ public static class Program Application.Start(p => { dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - SynchronizationContext context = new DispatcherQueueSynchronizationContext(dispatcherQueue); + context = new DispatcherQueueSynchronizationContext(dispatcherQueue); SynchronizationContext.SetSynchronizationContext(context); _ = new App(); }); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs index e151d575..8789c9f2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs @@ -90,14 +90,15 @@ internal class InfoBarService : IInfoBarService PrepareInfoBarAndShow(InfoBarSeverity.Error, ex.GetType().Name, $"{title}\n{ex.Message}", delay); } - private void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay) + [SuppressMessage("", "VSTHRD100")] + private async void PrepareInfoBarAndShow(InfoBarSeverity severity, string? title, string? message, int delay) { if (infoBarStack is null) { return; } - infoBarStack.DispatcherQueue.TryEnqueue(() => PrepareInfoBarAndShowInternal(severity, title, message, delay)); + await PrepareInfoBarAndShowInternalAsync(severity, title, message, delay).ConfigureAwait(false); } /// @@ -107,9 +108,10 @@ internal class InfoBarService : IInfoBarService /// 标题 /// 消息 /// 关闭延迟 - [SuppressMessage("", "VSTHRD100", Justification = "只能通过 async void 方法使控件在主线程创建")] - private async void PrepareInfoBarAndShowInternal(InfoBarSeverity severity, string? title, string? message, int delay) + private async Task PrepareInfoBarAndShowInternalAsync(InfoBarSeverity severity, string? title, string? message, int delay) { + await Program.SwitchToMainThreadAsync(); + InfoBar infoBar = new() { Severity = severity, @@ -128,12 +130,12 @@ internal class InfoBarService : IInfoBarService } } - private void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args) + [SuppressMessage("", "VSTHRD100")] + private async void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args) { - Must.NotNull(infoBarStack!).DispatcherQueue.TryEnqueue(() => - { - infoBarStack.Children.Remove(sender); - sender.Closed -= OnInfoBarClosed; - }); + await Program.SwitchToMainThreadAsync(); + + Must.NotNull(infoBarStack!).Children.Remove(sender); + sender.Closed -= OnInfoBarClosed; } }