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#

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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