尽管已经编写了多年的C#,但我没有async的专家,但是在阅读了一些MSDN博客文章后AFAICT:
Task
)可以捕获或不捕获当前SynchronizationContext
。SynchronizationContext
大致对应一个帖子:如果我在UI线程上并调用await task
,其中'流动上下文',继续运行在UI线程。如果我调用await task.ConfigureAwait(false)
,则继续运行在某个随机线程池线程上,该线程可能是/可能不是UI线程。OnCompleted
流式上下文,而UnsafeOnCompleted
不会传输上下文。好了,有了这个,我们来看看Roslyn为await Task.Yield()
生成的代码。这样:
using System;
using System.Threading.Tasks;
public class C {
public async void M() {
await Task.Yield();
}
}
此编译器生成的代码中的结果(您可以自己验证here):
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调用AwaitUnsafeOnCompleted
,而不是AwaitOnCompleted
。反过来,AwaitUnsafeOnCompleted
会在等待者身上拨打UnsafeOnCompleted
。 YieldAwaiter
does not在UnsafeOnCompleted
中传输当前上下文。
这真让我感到困惑,因为this question似乎暗示Task.Yield
捕获当前的上下文;提问者对缺乏一个没有版本的版本感到沮丧。所以我很困惑:是或不是Yield
捕获当前的背景?
如果它没有,我怎么强迫它?我在UI线程上调用此方法,我真的需要继续在UI线程上运行。 YieldAwaitable
缺少ConfigureAwait()
方法,因此我无法撰写await Task.Yield().ConfigureAwait(true)
。
谢谢!
答案 0 :(得分:5)
如上所述in the comments,回答问题的一种简单方法就是运行代码并查看会发生什么。您会发现在原始上下文中恢复执行。
我认为你被红鲱鱼分心了。是的,AwaitUnsafeOnCompleted()
调用了UnsafeOnCompleted()
,false
会将flowContext
参数的QueueContinuation()
传递给AsyncMethodBuilderCore
方法。但所有这些都忽略了Action
对象用于创建延续AsyncVoidMethodBuilder
,creates that Action
by capturing the context的事实,因此延续可以在原始上下文中执行。
状态机中使用的await
是什么(至少就你的问题而言)并不重要,因为创建的continuation本身会处理回到原始上下文。
事实上,这是await
的核心功能。如果默认情况下,某些async
语句在捕获的上下文中继续,而其他语句则没有,那么API将会被打破。 await
/ [Any]
如此强大的一个主要原因是,它不仅允许以线性,同步出现的方式编写使用异步操作的代码,它基本上消除了我们过去常常遇到的所有麻烦在完成某些异步操作时,我们试图回到特定的上下文(例如UI线程或ASP.NET上下文)。
答案 1 :(得分:4)
总而言之,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
或{{1} })和TaskScheduler
用于确定是否需要流动flowExecutionContext
。