Task.Yield是否真的在当前上下文中运行延续?

Jam*_* Ko 6 .net c# asynchronous task async-await

尽管已经编写了多年的C#,但我不是async的专家,但是在阅读了一些MSDN博客文章之后,AFAICT:

  • 等待(例如Task)可以捕获或不捕获当前SynchronizationContext.
  • 一个SynchronizationContext大致相当于一个线程:如果我在UI线程和呼叫上await task,这"流动语境",延续在UI线程上运行.如果我调用await task.ConfigureAwait(false),则继续在一些随机线程池线程上运行,该线程可能是/可能不是UI线程.
  • 对于awaiters:OnCompleted流上下文,UnsafeOnCompleted并不流动上下文.

好了,有了这个,我们来看看Roslyn生成的代码await Task.Yield().这个:

using System;
using System.Threading.Tasks;

public class C {
    public async void M() {
        await Task.Yield();
    }
}
Run Code Online (Sandbox Code Playgroud)

这个编译器生成的代码的结果(你可以在这里验证自己):

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__);
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,AwaitUnsafeOnCompleted正在使用awaiter调用,而不是AwaitOnCompleted.AwaitUnsafeOnCompleted反过来,呼叫UnsafeOnCompleted等待者.YieldAwaiter 不会流入当前的上下文UnsafeOnCompleted.

这真让我感到困惑,因为这个问题似乎意味着它Task.Yield 确实捕获了当前的背景; 提问者很沮丧,因为缺乏一个没有的版本.所以我很困惑:是否Yield捕获当前上下文?

如果没有,我怎么强迫它?我在UI线程上调用这个方法,我真的需要继续在UI线程上运行.YieldAwaitable缺乏ConfigureAwait()方法,所以我不能写await Task.Yield().ConfigureAwait(true).

谢谢!

Pet*_*iho 5

正如评论中所指出,回答问题的一个简单方法就是运行代码并查看会发生什么.您会发现在原始上下文中恢复执行.

我觉得你被红鲱鱼分心了.是的,AwaitUnsafeOnCompleted()调用UnsafeOnCompleted(),进而通过falseflowContext参数的QueueContinuation()方法.但是,这一切忽视的事实AsyncMethodBuilderCore使用的对象创建的延续Action,创建Action通过捕获背景下,这样的延续,可以在原来的上下文中执行.

AsyncVoidMethodBuilder状态机中使用的是什么(至少就你的问题而言)并不重要,因为创建的延续本身会处理回到原始上下文.

事实上,这是一个核心特征await.如果默认情况下,某些await语句在捕获的上下文中继续,而其他语句则没有,那么API将会被打破.一个主要原因async/ await如此强大,它不仅允许编写以线性,同步出现的方式使用异步操作的代码,它基本上消除了我们曾经试图回到特定上下文的所有麻烦(例如UI线程或ASP.NET上下文)完成一些异步操作.


Ste*_*ary 5

TheExecutionContextawait(通常是 a SynchronizationContext)捕获的上下文不同

总而言之,ExecutionContext 必须始终为开发人员代码流;否则是一个安全问题。在某些情况下(例如,在编译器生成的代码中),编译器知道流动是安全的(即,它将通过另一种机制流动)。正如 Peter 所追踪的,这基本上就是这种情况下发生的事情。

但是,这与由await(当前SynchronizationContextTaskScheduler)捕获的上下文没有任何关系。看一看中的逻辑YieldAwaiter.QueueContinuation:如果有当前的SynchronizationContextor TaskScheduler,它总是被使用flowContext参数被忽略。这是因为该flowContext参数仅指的是流动的ExecutionContext而不是SynchronizationContext/ TaskScheduler

相比之下,任务等待者结束于Task.SetContinuationForAwait,它有两个bool参数:continueOnCapturedContext用于确定是否捕获await上下文(SynchronizationContextTaskScheduler),以及flowExecutionContext用于确定是否有必要将ExecutionContext.