support Send on sync context

This commit is contained in:
Lightczx
2023-10-13 15:27:46 +08:00
parent 4e57520115
commit c0165c57fd
11 changed files with 123 additions and 103 deletions

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using Microsoft.Windows.ApplicationModel.Resources;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Service; using Snap.Hutao.Service;
using System.Globalization; using System.Globalization;

View File

@@ -29,11 +29,14 @@ internal sealed partial class ExceptionRecorder
private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) private void OnAppUnhandledException(object? sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
{ {
serviceProvider ValueTask<string?> task = serviceProvider
.GetRequiredService<Web.Hutao.Log.HomaLogUploadClient>() .GetRequiredService<Web.Hutao.Log.HomaLogUploadClient>()
.UploadLogAsync(e.Exception) .UploadLogAsync(e.Exception);
.GetAwaiter()
.GetResult(); if (!task.IsCompleted)
{
task.GetAwaiter().GetResult();
}
logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception)); logger.LogError("未经处理的全局异常:\r\n{Detail}", ExceptionFormat.Format(e.Exception));
} }

View File

@@ -15,7 +15,7 @@ internal sealed class RuntimeEnvironmentException : Exception
/// </summary> /// </summary>
/// <param name="message">消息</param> /// <param name="message">消息</param>
/// <param name="innerException">内部错误</param> /// <param name="innerException">内部错误</param>
public RuntimeEnvironmentException(string message, Exception innerException) public RuntimeEnvironmentException(string message, Exception? innerException)
: base($"{message}\n{innerException.Message}", innerException) : base($"{message}\n{innerException.Message}", innerException)
{ {
} }

View File

@@ -14,27 +14,27 @@ namespace Snap.Hutao.Core.ExceptionService;
[System.Diagnostics.StackTraceHidden] [System.Diagnostics.StackTraceHidden]
internal static class ThrowHelper internal static class ThrowHelper
{ {
/// <summary>
/// 操作取消
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="OperationCanceledException">操作取消异常</exception>
[DoesNotReturn] [DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default) public static ArgumentException Argument(string message, string? paramName)
{ {
throw new OperationCanceledException(message, inner); throw new ArgumentException(message, paramName);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception? inner)
{
throw new DatabaseCorruptedException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static GameFileOperationException GameFileOperation(string message, Exception? inner)
{
throw new GameFileOperationException(message, inner);
} }
/// <summary>
/// 无效操作
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="InvalidOperationException">无效操作异常</exception>
[DoesNotReturn] [DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static InvalidOperationException InvalidOperation(string message, Exception? inner = default) public static InvalidOperationException InvalidOperation(string message, Exception? inner = default)
@@ -42,71 +42,38 @@ internal static class ThrowHelper
throw new InvalidOperationException(message, inner); throw new InvalidOperationException(message, inner);
} }
/// <summary>
/// 游戏文件操作失败
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="GameFileOperationException">文件操作失败</exception>
public static GameFileOperationException GameFileOperation(string message, Exception inner)
{
throw new GameFileOperationException(message, inner);
}
/// <summary>
/// 包转换错误
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="PackageConvertException">包转换错误异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static PackageConvertException PackageConvert(string message, Exception inner)
{
throw new PackageConvertException(message, inner);
}
/// <summary>
/// 用户数据损坏
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="UserdataCorruptedException">数据损坏</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static UserdataCorruptedException UserdataCorrupted(string message, Exception inner)
{
throw new UserdataCorruptedException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static DatabaseCorruptedException DatabaseCorrupted(string message, Exception inner)
{
throw new DatabaseCorruptedException(message, inner);
}
/// <summary>
/// 运行环境异常
/// </summary>
/// <param name="message">消息</param>
/// <param name="inner">内部错误</param>
/// <returns>nothing</returns>
/// <exception cref="RuntimeEnvironmentException">环境异常</exception>
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception inner)
{
throw new RuntimeEnvironmentException(message, inner);
}
[DoesNotReturn] [DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
public static NotSupportedException NotSupported() public static NotSupportedException NotSupported()
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static OperationCanceledException OperationCanceled(string message, Exception? inner = default)
{
throw new OperationCanceledException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static PackageConvertException PackageConvert(string message, Exception? inner)
{
throw new PackageConvertException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static RuntimeEnvironmentException RuntimeEnvironment(string message, Exception? inner)
{
throw new RuntimeEnvironmentException(message, inner);
}
[DoesNotReturn]
[MethodImpl(MethodImplOptions.NoInlining)]
public static UserdataCorruptedException UserdataCorrupted(string message, Exception? inner)
{
throw new UserdataCorruptedException(message, inner);
}
} }

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.UI.Dispatching; using Microsoft.UI.Dispatching;
using System.Runtime.ExceptionServices;
namespace Snap.Hutao.Core.Threading; namespace Snap.Hutao.Core.Threading;
@@ -18,15 +19,35 @@ internal static class DispatcherQueueExtension
/// <param name="action">执行的回调</param> /// <param name="action">执行的回调</param>
public static void Invoke(this DispatcherQueue dispatcherQueue, Action action) public static void Invoke(this DispatcherQueue dispatcherQueue, Action action)
{ {
using (ManualResetEventSlim blockEvent = new()) if (dispatcherQueue.HasThreadAccess)
{
action();
return;
}
ExceptionDispatchInfo? exceptionDispatchInfo = null;
using (ManualResetEventSlim blockEvent = new(false))
{ {
dispatcherQueue.TryEnqueue(() => dispatcherQueue.TryEnqueue(() =>
{ {
action(); try
blockEvent.Set(); {
action();
}
catch (Exception ex)
{
ExceptionDispatchInfo.Capture(ex);
}
finally
{
blockEvent.Set();
}
}); });
blockEvent.Wait(); blockEvent.Wait();
#pragma warning disable CA1508
exceptionDispatchInfo?.Throw();
#pragma warning restore CA1508
} }
} }
} }

View File

@@ -0,0 +1,33 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Dispatching;
namespace Snap.Hutao.Core.Threading;
internal sealed class DispatcherQueueSynchronizationContextSendSupport : SynchronizationContext
{
private readonly DispatcherQueue dispatcherQueue;
public DispatcherQueueSynchronizationContextSendSupport(DispatcherQueue dispatcherQueue)
{
this.dispatcherQueue = dispatcherQueue;
}
public override void Post(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
dispatcherQueue.TryEnqueue(() => d(state));
}
public override void Send(SendOrPostCallback d, object? state)
{
ArgumentNullException.ThrowIfNull(d);
dispatcherQueue.Invoke(() => d(state));
}
public override SynchronizationContext CreateCopy()
{
return new DispatcherQueueSynchronizationContextSendSupport(dispatcherQueue);
}
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.Core.Threading;
[Injection(InjectAs.Singleton, typeof(ITaskContext))] [Injection(InjectAs.Singleton, typeof(ITaskContext))]
internal sealed class TaskContext : ITaskContext internal sealed class TaskContext : ITaskContext
{ {
private readonly DispatcherQueueSynchronizationContext dispatcherQueueSynchronizationContext; private readonly DispatcherQueueSynchronizationContextSendSupport synchronizationContext;
private readonly DispatcherQueue dispatcherQueue; private readonly DispatcherQueue dispatcherQueue;
/// <summary> /// <summary>
@@ -20,8 +20,8 @@ internal sealed class TaskContext : ITaskContext
public TaskContext() public TaskContext()
{ {
dispatcherQueue = DispatcherQueue.GetForCurrentThread(); dispatcherQueue = DispatcherQueue.GetForCurrentThread();
dispatcherQueueSynchronizationContext = new(dispatcherQueue); synchronizationContext = new(dispatcherQueue);
SynchronizationContext.SetSynchronizationContext(dispatcherQueueSynchronizationContext); SynchronizationContext.SetSynchronizationContext(synchronizationContext);
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -39,18 +39,11 @@ internal sealed class TaskContext : ITaskContext
/// <inheritdoc/> /// <inheritdoc/>
public void InvokeOnMainThread(Action action) public void InvokeOnMainThread(Action action)
{ {
if (dispatcherQueue.HasThreadAccess) dispatcherQueue.Invoke(action);
{
action();
}
else
{
dispatcherQueue.Invoke(action);
}
} }
public IProgress<T> CreateProgressForMainThread<T>(Action<T> handler) public IProgress<T> CreateProgressForMainThread<T>(Action<T> handler)
{ {
return new DispatcherQueueProgress<T>(handler, dispatcherQueueSynchronizationContext); return new DispatcherQueueProgress<T>(handler, synchronizationContext);
} }
} }

View File

@@ -14,7 +14,7 @@ internal sealed class GameFileOperationException : Exception
/// </summary> /// </summary>
/// <param name="message">消息</param> /// <param name="message">消息</param>
/// <param name="innerException">内部错误</param> /// <param name="innerException">内部错误</param>
public GameFileOperationException(string message, Exception innerException) public GameFileOperationException(string message, Exception? innerException)
: base(SH.ServiceGameFileOperationExceptionMessage.Format(message), innerException) : base(SH.ServiceGameFileOperationExceptionMessage.Format(message), innerException)
{ {
} }

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Service.Game.Package;
internal sealed class PackageConvertException : Exception internal sealed class PackageConvertException : Exception
{ {
/// <inheritdoc cref="Exception(string?, Exception?)"/> /// <inheritdoc cref="Exception(string?, Exception?)"/>
public PackageConvertException(string message, Exception innerException) public PackageConvertException(string message, Exception? innerException)
: base(message, innerException) : base(message, innerException)
{ {
} }

View File

@@ -39,19 +39,20 @@ internal class MiHoYoJSInterface
document.querySelector('body').appendChild(st); document.querySelector('body').appendChild(st);
"""; """;
private readonly SemaphoreSlim webMessageSemaphore = new(1);
private readonly Guid interfaceId = Guid.NewGuid(); private readonly Guid interfaceId = Guid.NewGuid();
private readonly IServiceProvider serviceProvider;
private readonly UserAndUid userAndUid; private readonly UserAndUid userAndUid;
private CoreWebView2 webView;
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly ILogger<MiHoYoJSInterface> logger; private readonly ILogger<MiHoYoJSInterface> logger;
private readonly SemaphoreSlim webMessageSemaphore = new(1);
private readonly TypedEventHandler<CoreWebView2, CoreWebView2WebMessageReceivedEventArgs> webMessageReceivedEventHandler; private readonly TypedEventHandler<CoreWebView2, CoreWebView2WebMessageReceivedEventArgs> webMessageReceivedEventHandler;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler; private readonly TypedEventHandler<CoreWebView2, CoreWebView2DOMContentLoadedEventArgs> domContentLoadedEventHandler;
private readonly TypedEventHandler<CoreWebView2, CoreWebView2NavigationStartingEventArgs> navigationStartingEventHandler; private readonly TypedEventHandler<CoreWebView2, CoreWebView2NavigationStartingEventArgs> navigationStartingEventHandler;
private CoreWebView2 webView;
public MiHoYoJSInterface(CoreWebView2 webView, UserAndUid userAndUid) public MiHoYoJSInterface(CoreWebView2 webView, UserAndUid userAndUid)
{ {
// 由于Webview2 的作用域特殊性,我们在此处直接使用根服务 // 由于Webview2 的作用域特殊性,我们在此处直接使用根服务

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Web.Request.Builder.Abstraction; using Snap.Hutao.Web.Request.Builder.Abstraction;
using System.Net.Http; using System.Net.Http;
using System.Text; using System.Text;
@@ -66,7 +67,7 @@ internal abstract class HttpContentSerializer : IHttpContentSerializer, IHttpCon
The content to be serialized does not match the specified type. The content to be serialized does not match the specified type.
Expected an instance of the class "{contentType.FullName}", but got "{actualContentType.FullName}". Expected an instance of the class "{contentType.FullName}", but got "{actualContentType.FullName}".
"""; """;
throw new ArgumentException(message, nameof(contentType)); ThrowHelper.Argument(message, nameof(contentType));
} }
// The contentType is optional. In that case, try to get the type on our own. // The contentType is optional. In that case, try to get the type on our own.