|
|
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)。");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|