c# – Task.Yield是否真的在当前上下文中运行了continuation?

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

> 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();
    }
}

这个编译器生成代码的结果(你可以自己验证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而不是AwaitOnCompleted调用AwaitUnsafeOnCompleted.反过来,AwaitUnsafeOnCompleted在awaiter上调用UnsafeOnCompleted. YieldAwaiter does not在UnsafeOnCompleted中流动当前上下文.

这真让我困惑,因为this question似乎暗示Task.Yield确实捕获了当前的上下文;提问者很沮丧,因为缺乏一个没有的版本.所以我很困惑:产量是否捕获当前的背景?

如果没有,我怎么强迫它?我在UI线程上调用这个方法,我真的需要继续在UI线程上运行. YieldAwaitable缺少一个ConfigureAwait()方法,因此我无法编写等待Task.Yield().ConfigureAwait(true).

谢谢!

解决方法

The 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.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...