> Awaitables(例如Task)可以捕获或不捕获当前的SynchronizationContext.
> SynchronizationContext大致对应一个线程:如果我在UI线程上并调用await任务,即“流上下文”,则继续在UI线程上运行.如果我调用await task.ConfigureAwait(false),则继续在某个随机线程池线程上运行,该线程可能是/可能不是UI线程.
>对于awaiters:OnCompleted流上下文,而UnsafeOnCompleted不流动上下文.
好了,有了这个,我们来看看Roslyn为等待Task.Yield()生成的代码.这个:
using System; using System.Threading.Tasks; public class C { public async void M() { await Task.Yield(); } }
public class C { [CompilerGenerated] [StructLayout(LayoutKind.Auto)] private struct <M>d__0 : IAsyncStateMachine { public int <>1__state; public AsyncVoidMethodBuilder <>t__builder; private YieldAwaitable.YieldAwaiter <>u__1; void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; try { YieldAwaitable.YieldAwaiter yieldAwaiter; if (num != 0) { yieldAwaiter = Task.Yield().GetAwaiter(); if (!yieldAwaiter.IsCompleted) { num = (this.<>1__state = 0); this.<>u__1 = yieldAwaiter; this.<>t__builder.AwaitUnsafeOnCompleted<YieldAwaitable.YieldAwaiter,C.<M>d__0>(ref yieldAwaiter,ref this); return; } } else { yieldAwaiter = this.<>u__1; this.<>u__1 = default(YieldAwaitable.YieldAwaiter); num = (this.<>1__state = -1); } yieldAwaiter.GetResult(); yieldAwaiter = default(YieldAwaitable.YieldAwaiter); } catch (Exception arg_6E_0) { Exception exception = arg_6E_0; this.<>1__state = -2; this.<>t__builder.SetException(exception); return; } this.<>1__state = -2; this.<>t__builder.SetResult(); } [DebuggerHidden] void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { this.<>t__builder.SetStateMachine(stateMachine); } } [AsyncStateMachine(typeof(C.<M>d__0))] public void M() { C.<M>d__0 <M>d__; <M>d__.<>t__builder = AsyncVoidMethodBuilder.Create(); <M>d__.<>1__state = -1; AsyncVoidMethodBuilder <>t__builder = <M>d__.<>t__builder; <>t__builder.Start<C.<M>d__0>(ref <M>d__); } }
请注意,使用awaiter而不是AwaitOnCompleted调用AwaitUnsafeOnCompleted.反过来,AwaitUnsafeOnCompleted在awaiter上调用UnsafeOnCompleted
. YieldAwaiter does not在UnsafeOnCompleted中流动当前上下文.
这真让我困惑,因为this question似乎暗示Task.Yield确实捕获了当前的上下文;提问者很沮丧,因为缺乏一个没有的版本.所以我很困惑:产量是否捕获当前的背景?
如果没有,我怎么强迫它?我在UI线程上调用这个方法,我真的需要继续在UI线程上运行. YieldAwaitable缺少一个ConfigureAwait()方法,因此我无法编写等待Task.Yield().ConfigureAwait(true).
谢谢!
解决方法
ExecutionContext
is not the same as the context captured by await
(which is usually a SynchronizationContext
).
总而言之,必须始终为开发人员代码提供ExecutionContext;否则是一个安全问题.存在某些情况(例如,在编译器生成的代码中),其中编译器知道不流动是安全的(即,它将由另一机制流动).这基本上就是在这种情况下发生的事情,as traced by Peter.
但是,这与await捕获的上下文(当前的SynchronizationContext或TaskScheduler)没有任何关系.查看logic in YieldAwaiter.QueueContinuation
:如果有当前的SynchronizationContext或TaskScheduler,则始终使用它并忽略flowContext参数.这是因为flowContext参数仅指流动ExecutionContext而不是SynchronizationContext / TaskScheduler.
相比之下,the task awaiters end up at Task.SetContinuationForAwait
具有两个bool参数:continueOnCapturedContext用于确定是否捕获await上下文(SynchronizationContext或TaskScheduler),以及flowExecutionContext用于确定是否需要流动ExecutionContext.