You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

160 lines
6.5 KiB
C#

1 month ago
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace GeoSigmaDrawLib
{
/// <summary>
/// 提供一个通用的、线程安全的UI调度器Dispatcher
/// 它允许任何后台线程安全地、解耦地在UI线程上执行代码
/// 支持“即发即忘”(Post) 和 “请求/响应”(RequestAsync) 模式。
/// </summary>
/// <remarks>
/// 此类是一个单例必须在UI线程上通过调用 <see cref="Initialize"/> 方法进行初始化。
/// </remarks>
public class UiDispatcher
{
private static readonly UiDispatcher InstanceValue = new UiDispatcher();
private SynchronizationContext uiContext = null;
private UiDispatcher()
{
}
/// <summary>
/// 获取 UiDispatcher 的全局单例实例。
/// </summary>
public static UiDispatcher Instance => InstanceValue;
/// <summary>
/// 初始化调度器。此方法必须在主UI线程上例如 Program.cs 或主窗体的构造函数/Load事件中调用一次。
/// </summary>
/// <param name="uiContext">UI线程的同步上下文。请传入 <see cref="SynchronizationContext.Current"/>。</param>
public void Initialize(SynchronizationContext uiContext)
{
this.uiContext = uiContext ?? throw new ArgumentNullException(nameof(uiContext));
}
/// <summary>
/// [即发即忘] 异步地在UI线程上执行一个无参数的委托。
/// 调用方线程不会等待执行完成。
/// </summary>
/// <param name="action">要在UI线程上执行的 <see cref="Action"/>。</param>
public void Post(Action action)
{
CheckInitialized();
uiContext.Post(_ => action(), null);
}
/// <summary>
/// [即发即忘] 异步地在UI线程上执行一个带参数的委托。
/// 调用方线程不会等待执行完成。
/// </summary>
/// <typeparam name="T">参数的类型。</typeparam>
/// <param name="action">要在UI线程上执行的 <see cref="Action{T}"/>。</param>
/// <param name="state">要传递给委托的参数(状态)。</param>
public void Post<T>(Action<T> action, T state)
{
CheckInitialized();
uiContext.Post(s => action((T)s), state);
}
/// <summary>
/// [请求/响应] 异步地在UI线程上执行一个“无参数、有返回值”的委托。
/// 调用方(任何线程)可以 <c>await</c> 此方法以非阻塞的方式获取UI线程的执行结果。
/// </summary>
/// <typeparam name="TResult">UI线程委托返回值的类型。</typeparam>
/// <param name="func">要在UI线程上执行并返回结果的 <see cref="Func{TResult}"/>。</param>
/// <returns>一个表示异步操作的任务Task其结果 <c>TResult</c> 为UI线程委托的返回值。</returns>
public Task<TResult> RequestAsync<TResult>(Func<TResult> func)
{
CheckInitialized();
var tcs = new TaskCompletionSource<TResult>();
uiContext.Post(
_ =>
{
try
{
TResult result = func();
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, null);
return tcs.Task;
}
/// <summary>
/// [请求/响应] 异步地在UI线程上执行一个“有参数、有返回值”的委托。
/// 调用方(任何线程)可以 <c>await</c> 此方法以非阻塞的方式获取UI线程的执行结果。
/// </summary>
/// <typeparam name="TInput">输入参数的类型。</typeparam>
/// <typeparam name="TResult">UI线程委托返回值的类型。</typeparam>
/// <param name="func">要在UI线程上执行并返回结果的 <see cref="Func{TInput, TResult}"/>。</param>
/// <param name="input">要传递给UI线程委托的参数。</param>
/// <returns>一个表示异步操作的任务Task其结果 <c>TResult</c> 为UI线程委托的返回值。</returns>
public Task<TResult> RequestAsync<TInput, TResult>(Func<TInput, TResult> func, TInput input)
{
CheckInitialized();
var tcs = new TaskCompletionSource<TResult>();
uiContext.Post(
state =>
{
try
{
TResult result = func((TInput)state);
tcs.SetResult(result);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, input);
return tcs.Task;
}
/// <summary>
/// [同步请求/响应] 同步地在UI线程上执行一个委托并立即返回结果。
/// <para>
/// 警告:此方法会 <b>阻塞</b> 调用方线程如果是非UI线程直到UI线程处理完毕。
/// 过度使用可能导致性能问题或线程死锁。请优先使用 <see cref="RequestAsync{TResult}(Func{TResult})"/>。
/// </para>
/// </summary>
/// <typeparam name="TResult">返回值的类型。</typeparam>
/// <param name="func">要在UI线程上执行并返回结果的 <see cref="Func{TResult}"/>。</param>
/// <returns>UI线程委托的返回值。</returns>
public TResult RequestSend<TResult>(Func<TResult> func)
{
CheckInitialized();
TResult result = default;
// 使用 Send 方法进行同步(阻塞)调用
uiContext.Send(
_ =>
{
result = func();
}, null);
return result;
}
/// <summary>
/// 检查调度器是否已成功初始化。
/// </summary>
/// <exception cref="InvalidOperationException">如果调度器尚未通过 <see cref="Initialize"/> 方法初始化,则抛出此异常。</exception>
private void CheckInitialized()
{
if (uiContext == null)
{
throw new InvalidOperationException("UiDispatcher尚未初始化。请确保在主UI线程上调用了 Initialize(SynchronizationContext.Current)。");
}
}
}
}