From e957ad07a18de4080ac0bdc918dd26cfc62c00a9 Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Thu, 8 Sep 2022 21:29:31 +0800 Subject: [PATCH] ConfigureAwait --- src/Snap.Hutao/.editorconfig | 12 ++ .../Snap.Hutao/Control/ContentDialog2.cs | 26 ----- .../Extension/ContentDialogExtensions.cs | 26 ++++- .../Control/Text/DescriptionTextBlock.cs | 2 +- .../Snap.Hutao/Core/LifeCycle/Activation.cs | 3 +- .../Snap.Hutao/Core/Logging/DatebaseLogger.cs | 3 +- .../CodeAnalysis/ThreadAccessAttribute.cs | 29 +++++ .../CodeAnalysis/ThreadAccessState.cs | 21 ++++ .../Snap.Hutao/Core/Threading/ThreadHelper.cs | 19 +++ .../Core/Windowing/SystemBackdrop.cs | 2 + src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 4 +- .../AchievementArchiveChangedMessage.cs | 1 + src/Snap.Hutao/Snap.Hutao/Program.cs | 16 +-- .../Service/Achievement/AchievementService.cs | 2 + .../Achievement/IAchievementService.cs | 2 + .../Snap.Hutao/Service/InfoBarService.cs | 5 +- .../Navigation/INavigationRecipient.cs | 4 +- .../Dialog/AchievementImportDialog.xaml.cs | 2 + .../Snap.Hutao/View/MainView.xaml.cs | 7 +- .../View/Page/AchievementPage.xaml.cs | 10 +- .../ViewModel/AchievementViewModel.cs | 109 ++++++++++-------- .../Http/DynamicSecretHttpClient.cs | 1 + .../Hk4e/Event/GachaInfo/GachaInfoClient.cs | 2 +- .../Event/GachaInfo/GachaLogConfigration.cs | 32 ++--- .../Web/Hoyolab/HttpClientCookieExtensions.cs | 1 + .../HttpRequestHeadersExtensions.cs | 2 +- 26 files changed, 221 insertions(+), 122 deletions(-) delete mode 100644 src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs rename src/Snap.Hutao/Snap.Hutao/Web/{ => Request}/HttpRequestHeadersExtensions.cs (94%) diff --git a/src/Snap.Hutao/.editorconfig b/src/Snap.Hutao/.editorconfig index 26eacb4e..2a13ab68 100644 --- a/src/Snap.Hutao/.editorconfig +++ b/src/Snap.Hutao/.editorconfig @@ -148,6 +148,18 @@ csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:silent csharp_style_unused_value_expression_statement_preference = discard_variable:silent +# CA1001: 具有可释放字段的类型应该是可释放的 +dotnet_diagnostic.CA1001.severity = warning + +# CA1309: 使用序数字符串比较 +dotnet_diagnostic.CA1309.severity = suggestion + +# CA1805: 避免进行不必要的初始化 +dotnet_diagnostic.CA1805.severity = suggestion + +# VSTHRD111: Use ConfigureAwait(bool) +dotnet_diagnostic.VSTHRD111.severity = suggestion + [*.vb] #### 命名样式 #### diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs b/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs deleted file mode 100644 index ce293009..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Control/ContentDialog2.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.Xaml.Interactivity; -using Snap.Hutao.Control.Behavior; - -namespace Snap.Hutao.Control; - -/// -/// 继承自 实现了某些便捷功能 -/// -internal class ContentDialog2 : ContentDialog -{ - /// - /// 构造一个新的对话框 - /// - /// 窗口 - public ContentDialog2(Window window) - { - DefaultStyleKey = typeof(ContentDialog); - XamlRoot = window.Content.XamlRoot; - Interaction.SetBehaviors(this, new() { new ContentDialogBehavior() }); - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs index 514cb957..473310e5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtensions.cs @@ -1,7 +1,11 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Microsoft.Xaml.Interactivity; +using Snap.Hutao.Control.Behavior; +using Snap.Hutao.Core.Threading; using Snap.Hutao.Extension; namespace Snap.Hutao.Control.Extension; @@ -11,13 +15,33 @@ namespace Snap.Hutao.Control.Extension; /// internal static class ContentDialogExtensions { + /// + /// 针对窗口进行初始化 + /// + /// 对话框 + /// 窗口 + /// 初始化完成的对话框 + public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window) + { + contentDialog.XamlRoot = window.Content.XamlRoot; + Interaction.SetBehaviors(contentDialog, new() { new ContentDialogBehavior() }); + + return contentDialog; + } + /// /// 阻止用户交互 /// /// 对话框 + /// 切换到主线程 /// 用于恢复用户交互 - public static IDisposable BlockInteraction(this ContentDialog contentDialog) + public static async ValueTask BlockAsync(this ContentDialog contentDialog, bool switchToMainThread = false) { + if (switchToMainThread) + { + await ThreadHelper.SwitchToMainThreadAsync(); + } + contentDialog.ShowAsync().AsTask().SafeForget(); return new ContentDialogHider(contentDialog); } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs index c861716b..91636fc1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs @@ -149,7 +149,7 @@ public class DescriptionTextBlock : ContentControl private void OnActualThemeChanged(FrameworkElement sender, object args) { - ApplyDescription((TextBlock)this.Content, Description); + ApplyDescription((TextBlock)Content, Description); } [StructLayout(LayoutKind.Explicit)] diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index b2a2882d..66feb7c5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.Windows.AppLifecycle; +using Snap.Hutao.Core.Threading; using Snap.Hutao.Extension; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Navigation; @@ -84,7 +85,7 @@ internal static class Activation { case "/import": { - await Program.SwitchToMainThreadAsync(); + await ThreadHelper.SwitchToMainThreadAsync(); INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri"); await Ioc.Default diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs index 55f15da6..537bfd33 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/DatebaseLogger.cs @@ -15,8 +15,7 @@ internal sealed partial class DatebaseLogger : ILogger /// Initializes a new instance of the class. /// /// The name of the logger. - /// 应用程序数据库上下文 - /// 上下文锁 + /// 日志队列 public DatebaseLogger(string name, LogEntryQueue logEntryQueue) { this.name = name; diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs new file mode 100644 index 00000000..1129b130 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessAttribute.cs @@ -0,0 +1,29 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Threading.CodeAnalysis; + +/// +/// 在复杂的异步方法环境下 +/// 指示方法的线程访问状态 +/// +[AttributeUsage(AttributeTargets.Method)] +internal class ThreadAccessAttribute : Attribute +{ + /// + /// 指示方法的进入线程访问状态 + /// + /// 进入状态 + public ThreadAccessAttribute(ThreadAccessState enter) + { + } + + /// + /// 指示方法的进入退出线程访问状态 + /// + /// 进入状态 + /// 离开状态 + public ThreadAccessAttribute(ThreadAccessState enter, ThreadAccessState leave) + { + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs new file mode 100644 index 00000000..abd46b2a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/CodeAnalysis/ThreadAccessState.cs @@ -0,0 +1,21 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Threading.CodeAnalysis; + +/// +/// 线程访问情况 +/// +internal enum ThreadAccessState +{ + /// + /// 任何线程均有可能访问该方法 + /// + AnyThread, + + /// + /// 仅主线程有机会访问该方法 + /// 仅允许主线程访问该方法 + /// + MainThread, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs new file mode 100644 index 00000000..3090c0ca --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Threading/ThreadHelper.cs @@ -0,0 +1,19 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Threading; + +/// +/// 线程帮助类 +/// +internal static class ThreadHelper +{ + /// + /// 异步切换到主线程 + /// + /// 等待体 + public static DispatherQueueSwitchOperation SwitchToMainThreadAsync() + { + return new(Program.DispatcherQueue!); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs index 70d0edca..c4f00235 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/SystemBackdrop.cs @@ -57,6 +57,8 @@ public class SystemBackdrop backdropController = new(); + // Mica Alt + // backdropController.Kind = MicaKind.BaseAlt; backdropController.AddSystemBackdropTarget(window.As()); backdropController.SetSystemBackdropConfiguration(configuration); diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index 1f196e64..daac94af 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -15,7 +15,6 @@ namespace Snap.Hutao; [Injection(InjectAs.Singleton)] public sealed partial class MainWindow : Window { - private readonly AppDbContext appDbContext; private readonly WindowManager windowManager; private readonly IMessenger messenger; @@ -26,9 +25,8 @@ public sealed partial class MainWindow : Window /// /// 数据库上下文 /// 消息器 - public MainWindow(AppDbContext appDbContext, IMessenger messenger) + public MainWindow(IMessenger messenger) { - this.appDbContext = appDbContext; this.messenger = messenger; InitializeComponent(); diff --git a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs index 88af10bb..f263ca0d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/AchievementArchiveChangedMessage.cs @@ -8,6 +8,7 @@ namespace Snap.Hutao.Message; /// /// 成就存档切换消息 /// +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] internal class AchievementArchiveChangedMessage : ValueChangedMessage { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index c0702732..0c36181c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -18,16 +18,12 @@ namespace Snap.Hutao; /// public static class Program { - private static volatile DispatcherQueue? dispatcherQueue; - /// - /// 异步切换到主线程 + /// 主线程队列 /// - /// 等待体 - public static DispatherQueueSwitchOperation SwitchToMainThreadAsync() - { - return new(dispatcherQueue!); - } + [EditorBrowsable(EditorBrowsableState.Never)] + [SuppressMessage("", "SA1401")] + internal static volatile DispatcherQueue? DispatcherQueue; [DllImport("Microsoft.ui.xaml.dll")] private static extern void XamlCheckProcessRequirements(); @@ -50,8 +46,8 @@ public static class Program private static void InitializeApp(ApplicationInitializationCallbackParams param) { - dispatcherQueue = DispatcherQueue.GetForCurrentThread(); - DispatcherQueueSynchronizationContext context = new(dispatcherQueue); + DispatcherQueue = DispatcherQueue.GetForCurrentThread(); + DispatcherQueueSynchronizationContext context = new(DispatcherQueue); SynchronizationContext.SetSynchronizationContext(context); _ = Ioc.Default.GetRequiredService(); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index e68830cb..ff728d5f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Context.Database; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.Logging; +using Snap.Hutao.Core.Threading; using Snap.Hutao.Model.InterChange.Achievement; using System.Collections.ObjectModel; using BindingAchievement = Snap.Hutao.Model.Binding.Achievement; @@ -87,6 +88,7 @@ internal class AchievementService : IAchievementService else { // Sync cache + await ThreadHelper.SwitchToMainThreadAsync(); archiveCollection.Add(newArchive); // Sync database diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs index a6487c9d..dd166633 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/IAchievementService.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Threading.CodeAnalysis; using Snap.Hutao.Model.InterChange.Achievement; using System.Collections.ObjectModel; using BindingAchievement = Snap.Hutao.Model.Binding.Achievement; @@ -70,5 +71,6 @@ internal interface IAchievementService /// /// 新存档 /// 存档添加结果 + [ThreadAccess(ThreadAccessState.AnyThread)] Task TryAddArchiveAsync(EntityArchive newArchive); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs index 8789c9f2..87ea680b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/InfoBarService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Core.Threading; using Snap.Hutao.Service.Abstraction; namespace Snap.Hutao.Service; @@ -110,7 +111,7 @@ internal class InfoBarService : IInfoBarService /// 关闭延迟 private async Task PrepareInfoBarAndShowInternalAsync(InfoBarSeverity severity, string? title, string? message, int delay) { - await Program.SwitchToMainThreadAsync(); + await ThreadHelper.SwitchToMainThreadAsync(); InfoBar infoBar = new() { @@ -133,7 +134,7 @@ internal class InfoBarService : IInfoBarService [SuppressMessage("", "VSTHRD100")] private async void OnInfoBarClosed(InfoBar sender, InfoBarClosedEventArgs args) { - await Program.SwitchToMainThreadAsync(); + await ThreadHelper.SwitchToMainThreadAsync(); Must.NotNull(infoBarStack!).Children.Remove(sender); sender.Closed -= OnInfoBarClosed; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs index 983e6438..4958951a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Navigation/INavigationRecipient.cs @@ -5,11 +5,13 @@ namespace Snap.Hutao.Service.Navigation; /// /// 导航消息接收器 +/// 用于通知异步导航完成 /// public interface INavigationRecipient { /// - /// 异步接收消息 + /// 异步接收导航消息 + /// 在此方法结束后才会通知导航服务导航完成 /// /// 导航数据 /// 接收处理结果是否成功 diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs index 3c136174..d7cfe003 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/AchievementImportDialog.xaml.cs @@ -44,6 +44,8 @@ public sealed partial class AchievementImportDialog : ContentDialog /// 导入选项 public async Task> GetImportOptionAsync() { + await ThreadHelper.SwitchToMainThreadAsync(); + ContentDialogResult result = await ShowAsync(); ImportOption option = (ImportOption)ImportModeSelector.SelectedIndex; diff --git a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs index 0bdeb2e5..d0f7f2b3 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/MainView.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.UI.Xaml.Controls; using Snap.Hutao.Core; using Snap.Hutao.Core.Logging; +using Snap.Hutao.Core.Threading; using Snap.Hutao.Extension; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Service.Navigation; @@ -48,14 +49,16 @@ public sealed partial class MainView : UserControl private async Task UpdateThemeAsync() { - await Program.SwitchToMainThreadAsync(); + // It seems that UISettings.ColorValuesChanged + // event can raise up on a background thread. + await ThreadHelper.SwitchToMainThreadAsync(); App current = Ioc.Default.GetRequiredService(); if (!ThemeHelper.Equals(current.RequestedTheme, RequestedTheme)) { ILogger logger = Ioc.Default.GetRequiredService>(); - logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, current.RequestedTheme); + logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] | App Theme [{app}]", RequestedTheme, current.RequestedTheme); // Update controls' theme which presents in the PopupRoot RequestedTheme = ThemeHelper.ApplicationToElement(current.RequestedTheme); diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs index 2863c4d6..9b094105 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml.cs @@ -32,18 +32,10 @@ public sealed partial class AchievementPage : CancellablePage { if (extra.Data != null) { - await ((INavigationRecipient)DataContext).ReceiveAsync(extra); + await ((INavigationRecipient)DataContext).ReceiveAsync(extra).ConfigureAwait(false); } extra.NotifyNavigationCompleted(); } } - - /// - protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) - { - ((AchievementViewModel)DataContext).SaveAchievements(); - - base.OnNavigatingFrom(e); - } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs index 66133320..2e47f582 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AchievementViewModel.cs @@ -6,9 +6,10 @@ using CommunityToolkit.Mvvm.Input; using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.WinUI.UI; using Microsoft.UI.Xaml.Controls; -using Snap.Hutao.Control; using Snap.Hutao.Control.Cancellable; using Snap.Hutao.Control.Extension; +using Snap.Hutao.Core.Threading; +using Snap.Hutao.Core.Threading.CodeAnalysis; using Snap.Hutao.Extension; using Snap.Hutao.Factory.Abstraction; using Snap.Hutao.Message; @@ -37,10 +38,11 @@ internal class AchievementViewModel : ObservableObject, ISupportCancellation, INavigationRecipient, - IRecipient, - IRecipient + IDisposable, + IRecipient { - private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.IsChecked), SortDirection.Ascending); + private static readonly SortDescription IncompletedItemsFirstSortDescription = + new(nameof(Model.Binding.Achievement.IsChecked), SortDirection.Ascending); private readonly IMetadataService metadataService; private readonly IAchievementService achievementService; @@ -89,8 +91,7 @@ internal class AchievementViewModel RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync); SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort); - messenger.Register(this); - messenger.Register(this); + messenger.Register(this); } /// @@ -190,22 +191,14 @@ internal class AchievementViewModel /// public ICommand SortIncompletedSwitchCommand { get; } - /// - public void Receive(MainWindowClosedMessage message) - { - SaveAchievements(); - } - /// public void Receive(AchievementArchiveChangedMessage message) { HandleArchiveChangeAsync(message.OldValue, message.NewValue).SafeForget(); } - /// - /// 保存当前用户的成就 - /// - public void SaveAchievements() + /// + public void Dispose() { if (Achievements != null && SelectedArchive != null) { @@ -216,11 +209,11 @@ internal class AchievementViewModel /// public async Task ReceiveAsync(INavigationData data) { - if (await openUICompletionSource.Task) + if (await openUICompletionSource.Task.ConfigureAwait(false)) { if (data.Data is "InvokeByUri") { - await ImportUIAFFromClipboardAsync(); + await ImportUIAFFromClipboardAsync().ConfigureAwait(false); return true; } } @@ -228,17 +221,22 @@ internal class AchievementViewModel return false; } + [ThreadAccess(ThreadAccessState.MainThread)] private static Task ShowImportFailDialogAsync(string message) { - return new ContentDialog2(Ioc.Default.GetRequiredService()) + ContentDialog dialog = new() { Title = "导入失败", Content = message, PrimaryButtonText = "确认", DefaultButton = ContentDialogButton.Primary, - }.ShowAsync().AsTask(); + }; + + MainWindow mainWindow = Ioc.Default.GetRequiredService(); + return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask(); } + [ThreadAccess(ThreadAccessState.MainThread)] private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve) { if (oldArchieve != null && Achievements != null) @@ -248,17 +246,19 @@ internal class AchievementViewModel if (newArchieve != null) { - await UpdateAchievementsAsync(newArchieve); + await UpdateAchievementsAsync(newArchieve).ConfigureAwait(false); } } + [ThreadAccess(ThreadAccessState.MainThread)] private async Task OpenUIAsync() { - bool metaInitialized = await metadataService.InitializeAsync(CancellationToken); + bool metaInitialized = await metadataService.InitializeAsync(CancellationToken).ConfigureAwait(false); if (metaInitialized) { - List goals = await metadataService.GetAchievementGoalsAsync(CancellationToken); + List goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false); + await ThreadHelper.SwitchToMainThreadAsync(); AchievementGoals = goals.OrderBy(goal => goal.Order).ToList(); Archives = achievementService.GetArchiveCollection(); @@ -274,24 +274,29 @@ internal class AchievementViewModel openUICompletionSource.TrySetResult(metaInitialized); } + [ThreadAccess(ThreadAccessState.AnyThread)] private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive) { - List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken); + List rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false); List combined = achievementService.GetAchievements(archive, rawAchievements); + + // Assemble achievements on the UI thread. + await ThreadHelper.SwitchToMainThreadAsync(); Achievements = new(combined, true); UpdateAchievementFilter(SelectedAchievementGoal); UpdateAchievementsSort(); } + [ThreadAccess(ThreadAccessState.MainThread)] private async Task AddArchiveAsync() { MainWindow mainWindow = Ioc.Default.GetRequiredService(); - (bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync(); + (bool isOk, string name) = await new AchievementArchiveCreateDialog(mainWindow).GetInputAsync().ConfigureAwait(false); if (isOk) { - ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name)); + ArchiveAddResult result = await achievementService.TryAddArchiveAsync(Model.Entity.AchievementArchive.Create(name)).ConfigureAwait(false); switch (result) { @@ -310,24 +315,26 @@ internal class AchievementViewModel } } + [ThreadAccess(ThreadAccessState.MainThread)] private async Task RemoveArchiveAsync() { if (Archives != null && SelectedArchive != null) { - MainWindow mainWindow = Ioc.Default.GetRequiredService(); - ContentDialogResult result = await new ContentDialog2(mainWindow) + ContentDialog dialog = new() { Title = $"确定要删除存档 {SelectedArchive.Name} 吗?", Content = "该操作是不可逆的,该存档和其内的所有成就状态会丢失。", PrimaryButtonText = "确认", CloseButtonText = "取消", DefaultButton = ContentDialogButton.Close, - } - .ShowAsync(); + }; + + MainWindow mainWindow = Ioc.Default.GetRequiredService(); + ContentDialogResult result = await dialog.InitializeWithWindow(mainWindow).ShowAsync(); if (result == ContentDialogResult.Primary) { - await achievementService.RemoveArchiveAsync(SelectedArchive); + await achievementService.RemoveArchiveAsync(SelectedArchive).ConfigureAwait(false); // reselect first archive SelectedArchive = Archives.FirstOrDefault(); @@ -335,25 +342,28 @@ internal class AchievementViewModel } } + [ThreadAccess(ThreadAccessState.AnyThread)] private async Task ImportUIAFFromClipboardAsync() { if (achievementService.CurrentArchive == null) { // TODO: automatically create a archive. - infoBarService.Information("必须选择一个用户才能导入成就"); + infoBarService.Information("必须创建一个用户才能导入成就"); return; } - if (await GetUIAFFromClipboardAsync() is UIAF uiaf) + if (await GetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf) { - await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf); + await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false); } else { - await ShowImportFailDialogAsync("数据格式不正确"); + await ThreadHelper.SwitchToMainThreadAsync(); + await ShowImportFailDialogAsync("数据格式不正确").ConfigureAwait(false); } } + [ThreadAccess(ThreadAccessState.MainThread)] private async Task ImportUIAFFromFileAsync() { if (achievementService.CurrentArchive == null) @@ -368,24 +378,27 @@ internal class AchievementViewModel if (await picker.PickSingleFileAsync() is StorageFile file) { - if (await GetUIAFFromFileAsync(file) is UIAF uiaf) + if (await GetUIAFFromFileAsync(file).ConfigureAwait(false) is UIAF uiaf) { - await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf); + await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false); } else { - await ShowImportFailDialogAsync("数据格式不正确"); + await ThreadHelper.SwitchToMainThreadAsync(); + await ShowImportFailDialogAsync("数据格式不正确").ConfigureAwait(false); } } } + [ThreadAccess(ThreadAccessState.AnyThread)] private async Task GetUIAFFromClipboardAsync() { UIAF? uiaf = null; string json; try { - json = await Clipboard.GetContent().GetTextAsync(); + Task task = Clipboard.GetContent().GetTextAsync().AsTask(); + json = await task.ConfigureAwait(false); } catch (COMException ex) { @@ -405,6 +418,7 @@ internal class AchievementViewModel return uiaf; } + [ThreadAccess(ThreadAccessState.AnyThread)] private async Task GetUIAFFromFileAsync(StorageFile file) { UIAF? uiaf = null; @@ -414,7 +428,7 @@ internal class AchievementViewModel { using (Stream stream = fileSream.AsStream()) { - uiaf = await JsonSerializer.DeserializeAsync(stream, options); + uiaf = await JsonSerializer.DeserializeAsync(stream, options).ConfigureAwait(false); } } } @@ -426,16 +440,18 @@ internal class AchievementViewModel return uiaf; } + [ThreadAccess(ThreadAccessState.AnyThread)] private async Task TryImportUIAFInternalAsync(Model.Entity.AchievementArchive archive, UIAF uiaf) { if (uiaf.IsCurrentVersionSupported()) { MainWindow mainWindow = Ioc.Default.GetRequiredService(); - (bool isOk, ImportOption option) = await new AchievementImportDialog(mainWindow, uiaf).GetImportOptionAsync(); + await ThreadHelper.SwitchToMainThreadAsync(); + (bool isOk, ImportOption option) = await new AchievementImportDialog(mainWindow, uiaf).GetImportOptionAsync().ConfigureAwait(true); if (isOk) { - ContentDialog2 importingDialog = new(mainWindow) + ContentDialog importingDialog = new() { Title = "导入成就中", Content = new ProgressBar() { IsIndeterminate = true }, @@ -443,19 +459,20 @@ internal class AchievementViewModel }; ImportResult result; - using (importingDialog.BlockInteraction()) + using (await importingDialog.InitializeWithWindow(mainWindow).BlockAsync().ConfigureAwait(false)) { - result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, option); + result = await achievementService.ImportFromUIAFAsync(archive, uiaf.List, option).ConfigureAwait(false); } infoBarService.Success(result.ToString()); - await UpdateAchievementsAsync(archive); + await UpdateAchievementsAsync(archive).ConfigureAwait(false); return true; } } else { - await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入"); + await ThreadHelper.SwitchToMainThreadAsync(); + await ShowImportFailDialogAsync("数据的 UIAF 版本过低,无法导入").ConfigureAwait(false); } return false; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs index 61c9088f..7dd31c13 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/DynamicSecret/Http/DynamicSecretHttpClient.cs @@ -3,6 +3,7 @@ using System.Net.Http; using System.Net.Http.Json; +using Snap.Hutao.Web.Request; namespace Snap.Hutao.Web.Hoyolab.DynamicSecret.Http; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs index 977d3b24..6c376957 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaInfoClient.cs @@ -38,4 +38,4 @@ internal class GachaInfoClient { return httpClient.GetFromJsonAsync>(ApiEndpoints.GachaInfoGetGachaLog(config.AsQuery()), options, token); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs index b1eb1b5e..5c56d8e6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Hk4e/Event/GachaInfo/GachaLogConfigration.cs @@ -12,6 +12,22 @@ public struct GachaLogConfigration { private readonly QueryString innerQuery; + /// + /// 构造一个新的祈愿记录请求配置 + /// + /// 原始查询字符串 + /// 祈愿类型 + /// 终止Id + public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL) + { + innerQuery = QueryString.Parse(query); + innerQuery.Set("lang", "zh-cn"); + + Size = 20; + Type = type; + EndId = endId; + } + /// /// 尺寸 /// @@ -36,22 +52,6 @@ public struct GachaLogConfigration set => innerQuery.Set("end_id", value); } - /// - /// 构造一个新的祈愿记录请求配置 - /// - /// 原始查询字符串 - /// 祈愿类型 - /// 终止Id - public GachaLogConfigration(string query, GachaConfigType type, ulong endId = 0UL) - { - innerQuery = QueryString.Parse(query); - innerQuery.Set("lang", "zh-cn"); - - Size = 20; - Type = type; - EndId = endId; - } - /// /// 转换到查询字符串 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs index 9e8d3762..8137c8d9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientCookieExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Binding; +using Snap.Hutao.Web.Request; using System.Net.Http; namespace Snap.Hutao.Web.Hoyolab; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs similarity index 94% rename from src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs rename to src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs index 38329d90..eceaa901 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/HttpRequestHeadersExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Request/HttpRequestHeadersExtensions.cs @@ -3,7 +3,7 @@ using System.Net.Http.Headers; -namespace Snap.Hutao.Web; +namespace Snap.Hutao.Web.Request; /// /// 扩展