Created
July 11, 2018 12:55
-
-
Save walterlv/ca0fc857eae04c1088aebcb8d636d1cb to your computer and use it in GitHub Desktop.
Custom awaiter with background UI thread
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Runtime.CompilerServices; | |
namespace Walterlv.Threading | |
{ | |
/// <summary> | |
/// 表示一个可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 `await` 异步等待。 | |
/// </summary> | |
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter 的实例。</typeparam> | |
public interface IAwaitable<out TAwaiter> where TAwaiter : IAwaiter | |
{ | |
/// <summary> | |
/// 获取一个可用于 await 关键字异步等待的异步等待对象。 | |
/// 此方法会被编译器自动调用。 | |
/// </summary> | |
TAwaiter GetAwaiter(); | |
} | |
/// <summary> | |
/// 表示一个包含返回值的可等待对象,如果一个方法返回此类型的实例,则此方法可以使用 `await` 异步等待返回值。 | |
/// </summary> | |
/// <typeparam name="TAwaiter">用于给 await 确定返回时机的 IAwaiter{<typeparamref name="TResult"/>} 的实例。</typeparam> | |
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam> | |
public interface IAwaitable<out TAwaiter, out TResult> where TAwaiter : IAwaiter<TResult> | |
{ | |
/// <summary> | |
/// 获取一个可用于 await 关键字异步等待的异步等待对象。 | |
/// 此方法会被编译器自动调用。 | |
/// </summary> | |
TAwaiter GetAwaiter(); | |
} | |
/// <summary> | |
/// 用于给 await 确定异步返回的时机。 | |
/// </summary> | |
public interface IAwaiter : INotifyCompletion | |
{ | |
/// <summary> | |
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。 | |
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true,或者始终为 false。 | |
/// </summary> | |
bool IsCompleted { get; } | |
/// <summary> | |
/// 此方法会被编译器在 await 结束时自动调用以获取返回状态(包括异常)。 | |
/// </summary> | |
void GetResult(); | |
} | |
/// <summary> | |
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时, | |
/// 用于给 await 确定异步返回的时机。 | |
/// </summary> | |
public interface ICriticalAwaiter : IAwaiter, ICriticalNotifyCompletion | |
{ | |
} | |
/// <summary> | |
/// 用于给 await 确定异步返回的时机,并获取到返回值。 | |
/// </summary> | |
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam> | |
public interface IAwaiter<out TResult> : INotifyCompletion | |
{ | |
/// <summary> | |
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常);此状态会被编译器自动调用。 | |
/// 在实现中,为了达到各种效果,可以灵活应用其值:可以始终为 true,或者始终为 false。 | |
/// </summary> | |
bool IsCompleted { get; } | |
/// <summary> | |
/// 获取此异步等待操作的返回值,此方法会被编译器在 await 结束时自动调用以获取返回值(包括异常)。 | |
/// </summary> | |
/// <returns>异步操作的返回值。</returns> | |
TResult GetResult(); | |
} | |
/// <summary> | |
/// 当执行关键代码(此代码中的错误可能给应用程序中的其他状态造成负面影响)时, | |
/// 用于给 await 确定异步返回的时机,并获取到返回值。 | |
/// </summary> | |
/// <typeparam name="TResult">异步返回的返回值类型。</typeparam> | |
public interface ICriticalAwaiter<out TResult> : IAwaiter<TResult>, ICriticalNotifyCompletion | |
{ | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.ExceptionServices; | |
using System.Windows.Threading; | |
using Walterlv.Annotations; | |
using Walterlv.Threading; | |
namespace Walterlv.Demo.Utils.Threading | |
{ | |
/// <summary> | |
/// 表示可以等待一个主要运行在 UI 线程的异步操作。 | |
/// </summary> | |
/// <typeparam name="T">异步等待 UI 操作结束后的返回值类型。</typeparam> | |
public class DispatcherAsyncOperation<T> : DispatcherObject, | |
IAwaitable<DispatcherAsyncOperation<T>, T>, IAwaiter<T> | |
{ | |
/// <summary> | |
/// 创建 <see cref="DispatcherAsyncOperation{T}"/> 的新实例。 | |
/// </summary> | |
private DispatcherAsyncOperation() | |
{ | |
} | |
/// <summary> | |
/// 获取一个可用于 await 关键字异步等待的异步等待对象。 | |
/// 此方法会被编译器自动调用。 | |
/// </summary> | |
/// <returns>返回自身,用于异步等待返回值。</returns> | |
public DispatcherAsyncOperation<T> GetAwaiter() | |
{ | |
return this; | |
} | |
/// <summary> | |
/// 获取一个状态,该状态表示正在异步等待的操作已经完成(成功完成或发生了异常)。 | |
/// 此状态会被编译器自动调用。 | |
/// </summary> | |
public bool IsCompleted { get; private set; } | |
/// <summary> | |
/// 获取此异步等待操作的返回值。 | |
/// 与 <see cref="System.Threading.Tasks.Task{TResult}"/> 不同的是, | |
/// 如果操作没有完成或发生了异常,此实例会返回 <typeparamref name="T"/> 的默认值, | |
/// 而不是阻塞线程直至任务完成。 | |
/// </summary> | |
public T Result { get; private set; } | |
/// <summary> | |
/// 获取此异步等待操作的返回值,此方法会被编译器在 await 结束时自动调用以获取返回值。 | |
/// 与 <see cref="System.Threading.Tasks.Task{TResult}"/> 不同的是, | |
/// 如果操作没有完成,此实例会返回 <typeparamref name="T"/> 的默认值,而不是阻塞线程直至任务完成。 | |
/// 但是,如果异步操作中发生了异常,调用此方法会抛出这个异常。 | |
/// </summary> | |
/// <returns> | |
/// 异步操作的返回值。 | |
/// </returns> | |
public T GetResult() | |
{ | |
if (_exception != null) | |
{ | |
ExceptionDispatchInfo.Capture(_exception).Throw(); | |
} | |
return Result; | |
} | |
/// <summary> | |
/// 使用 Builder 模式配置此异步操作执行完后,后续任务执行采用的优先级。 | |
/// 不配置时,使用的是 <see cref="DispatcherPriority.Normal"/>。 | |
/// </summary> | |
/// <param name="priority">使用 <see cref="Dispatcher"/> 调度的后续任务的优先级。</param> | |
/// <returns>实例自身。</returns> | |
public DispatcherAsyncOperation<T> ConfigurePriority(DispatcherPriority priority) | |
{ | |
_priority = priority; | |
return this; | |
} | |
/// <summary> | |
/// 当使用此类型执行异步任务的方法执行完毕后,编译器会自动调用此方法。 | |
/// 也就是说,此方法会在调用方所在的线程执行,用于通知调用方所在线程的代码已经执行完毕,请求执行 await 后续任务。 | |
/// 在此类型中,后续任务是通过 <see cref="Dispatcher.InvokeAsync(Action, DispatcherPriority)"/> 来执行的。 | |
/// </summary> | |
/// <param name="continuation"> | |
/// 被异步任务状态机包装的后续任务。当执行时,会让状态机继续往下走一步。 | |
/// </param> | |
public void OnCompleted(Action continuation) | |
{ | |
if (IsCompleted) | |
{ | |
// 如果 await 开始时任务已经执行完成,则直接执行 await 后面的代码。 | |
// 注意,即便 _continuation 有值,也无需关心,因为报告结束的时候就会将其执行。 | |
continuation?.Invoke(); | |
} | |
else | |
{ | |
// 当使用多个 await 关键字等待此同一个 awaitable 实例时,此 OnCompleted 方法会被多次执行。 | |
// 当任务真正结束后,需要将这些所有的 await 后面的代码都执行。 | |
_continuation += continuation; | |
} | |
} | |
/// <summary> | |
/// 调用此方法以报告任务结束,并指定返回值和异步任务中的异常。 | |
/// 当使用 <see cref="Create"/> 静态方法创建此类型的实例后,调用方可以通过方法参数中传出的委托来调用此方法。 | |
/// </summary> | |
/// <param name="result">异步返回值。</param> | |
/// <param name="exception">异步操作中的异常。</param> | |
private void ReportResult(T result, Exception exception) | |
{ | |
Result = result; | |
_exception = exception; | |
IsCompleted = true; | |
// _continuation 可能为 null,说明任务已经执行完毕,但没有任何一处 await 了这个任务。 | |
if (_continuation != null) | |
{ | |
// 无论此方法执行时所在线程关联的 Dispatcher 是否等于此类型创建时的 Dispatcher; | |
// 都 Invoke 到创建时的 Dispatcher 上,以便对当前执行上下文造成影响在不同线程执行下都一致(如异常)。 | |
Dispatcher.InvokeAsync(_continuation, _priority); | |
} | |
} | |
/// <summary> | |
/// 临时保存 await 后后续任务的包装,用于报告任务完成后能够继续执行。 | |
/// </summary> | |
private Action _continuation; | |
/// <summary> | |
/// 临时保存异步任务执行过程中发生的异常。它会在异步等待结束后抛出,以报告异步执行过程中发生的错误。 | |
/// </summary> | |
private Exception _exception; | |
/// <summary> | |
/// 储存恢复 await 后续任务时需要使用的优先级。 | |
/// </summary> | |
private DispatcherPriority _priority = DispatcherPriority.Normal; | |
/// <summary> | |
/// 创建 <see cref="DispatcherAsyncOperation{T}"/> 的新实例,并得到一个可以用于报告操作执行完毕的委托。 | |
/// </summary> | |
/// <param name="reportResult">一个委托。调用此委托可以报告任务已经执行完毕,并给定返回值和异常信息。</param> | |
/// <returns> | |
/// 创建好的 <see cref="DispatcherAsyncOperation{T}"/> 的新实例,将此返回值作为方法的返回值可以让方法支持 await 异步等待。 | |
/// </returns> | |
public static DispatcherAsyncOperation<T> Create([NotNull] out Action<T, Exception> reportResult) | |
{ | |
var asyncOperation = new DispatcherAsyncOperation<T>(); | |
reportResult = asyncOperation.ReportResult; | |
return asyncOperation; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Runtime.ExceptionServices; | |
using System.Threading; | |
using System.Windows.Threading; | |
using Walterlv.Annotations; | |
using Walterlv.Demo.Utils.Threading; | |
namespace Walterlv.Demo | |
{ | |
/// <summary> | |
/// 包含扩展 <see cref="Dispatcher"/> 的一些方法。 | |
/// </summary> | |
public static class UIDispatcher | |
{ | |
/// <summary> | |
/// 创建一个可以运行 <see cref="Dispatcher"/> 的后台 UI 线程,并返回这个线程的调度器 <see cref="Dispatcher"/>。 | |
/// </summary> | |
/// <param name="name">线程的名称,如果不指定,将使用 “BackgroundUI”。</param> | |
/// <returns>一个可以异步等待的 <see cref="Dispatcher"/>。</returns> | |
public static DispatcherAsyncOperation<Dispatcher> RunNewAsync([CanBeNull] string name = null) | |
{ | |
// 创建一个可等待的异步操作。 | |
var awaiter = DispatcherAsyncOperation<Dispatcher>.Create(out var reportResult); | |
// 记录原线程关联的 Dispatcher,以便在意外时报告异常。 | |
var originDispatcher = Dispatcher.CurrentDispatcher; | |
// 创建后台线程。 | |
var thread = new Thread(() => | |
{ | |
try | |
{ | |
// 获取关联此后台线程的 Dispatcher。 | |
var dispatcher = Dispatcher.CurrentDispatcher; | |
// 设置此线程的 SynchronizationContext,以便此线程上 await 之后能够返回此线程。 | |
SynchronizationContext.SetSynchronizationContext( | |
new DispatcherSynchronizationContext(dispatcher)); | |
// 报告 Dispatcher 已创建完毕,使用 await 异步等待 Dispatcher 创建的地方可以继续执行了。 | |
reportResult(dispatcher, null); | |
} | |
catch (Exception ex) | |
{ | |
// 报告创建过程中发生的异常。 | |
// 不需要担心其内部发生的异常,因为会被异步状态机捕获后重新在原线程上抛出。 | |
reportResult(null, ex); | |
} | |
// 此线程的以下代码将脱离异步状态机的控制,需要自己处理异常。 | |
try | |
{ | |
// 启动 Dispatcher,开始此线程上消息的调度。 | |
Dispatcher.Run(); | |
} | |
catch (Exception ex) | |
{ | |
// 如果新的 Dispatcher 线程上出现了未处理的异常,则将其抛到原调用线程上。 | |
originDispatcher.InvokeAsync(() => ExceptionDispatchInfo.Capture(ex).Throw()); | |
} | |
}) | |
{ | |
Name = name ?? "BackgroundUI", | |
IsBackground = true, | |
}; | |
thread.SetApartmentState(ApartmentState.STA); | |
thread.Start(); | |
return awaiter; | |
} | |
/// <summary> | |
/// 创建一个可以运行 <see cref="Dispatcher"/> 的后台 UI 线程,并返回这个线程的调度器 <see cref="Dispatcher"/>。 | |
/// </summary> | |
/// <param name="name">线程的名称,如果不指定,将使用 “BackgroundUI”。</param> | |
/// <returns>后台线程创建并启动的 <see cref="Dispatcher"/>。</returns> | |
public static Dispatcher RunNew([CanBeNull] string name = null) | |
{ | |
var resetEvent = new AutoResetEvent(false); | |
// 记录原线程关联的 Dispatcher,以便在意外时报告异常。 | |
var originDispatcher = Dispatcher.CurrentDispatcher; | |
Exception innerException = null; | |
Dispatcher dispatcher = null; | |
// 创建后台线程。 | |
var thread = new Thread(() => | |
{ | |
try | |
{ | |
// 获取关联此后台线程的 Dispatcher。 | |
dispatcher = Dispatcher.CurrentDispatcher; | |
// 设置此线程的 SynchronizationContext,以便此线程上 await 之后能够返回此线程。 | |
SynchronizationContext.SetSynchronizationContext( | |
new DispatcherSynchronizationContext(dispatcher)); | |
// 报告 Dispatcher 已创建完毕,使用 ResetEvent 同步等待 Dispatcher 创建的地方可以继续执行了。 | |
resetEvent.Set(); | |
} | |
catch (Exception ex) | |
{ | |
// 报告创建过程中发生的异常。 | |
innerException = ex; | |
resetEvent.Set(); | |
} | |
// 此线程的以下代码将脱离异步状态机的控制,需要自己处理异常。 | |
try | |
{ | |
// 启动 Dispatcher,开始此线程上消息的调度。 | |
Dispatcher.Run(); | |
} | |
catch (Exception ex) | |
{ | |
// 如果新的 Dispatcher 线程上出现了未处理的异常,则将其抛到原调用线程上。 | |
originDispatcher.InvokeAsync(() => ExceptionDispatchInfo.Capture(ex).Throw()); | |
} | |
}) | |
{ | |
Name = name ?? "BackgroundUI", | |
IsBackground = true, | |
}; | |
thread.SetApartmentState(ApartmentState.STA); | |
thread.Start(); | |
resetEvent.WaitOne(); | |
resetEvent.Dispose(); | |
resetEvent = null; | |
if (innerException != null) | |
{ | |
ExceptionDispatchInfo.Capture(innerException).Throw(); | |
} | |
return dispatcher; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment