问题 了解TaskScheduler.Current的行为


这是一个简单的WinForms应用程序:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void button1_Click(object sender, EventArgs e)
        {
            var ts = TaskScheduler.FromCurrentSynchronizationContext();
            await Task.Factory.StartNew(async () =>
            {
                Debug.WriteLine(new
                {
                    where = "1) before await",
                    currentTs = TaskScheduler.Current,
                    thread = Thread.CurrentThread.ManagedThreadId,
                    context = SynchronizationContext.Current
                });

                await Task.Yield(); // or await Task.Delay(1)

                Debug.WriteLine(new
                {
                    where = "2) after await",
                    currentTs = TaskScheduler.Current,
                    thread = Thread.CurrentThread.ManagedThreadId,
                    context = SynchronizationContext.Current
                });

            }, CancellationToken.None, TaskCreationOptions.None, scheduler: ts).Unwrap();
        }
    }
}

调试输出(单击按钮时):

{where = 1}在await之前,currentTs = System.Threading.Tasks.SynchronizationContextTaskScheduler,thread = 9,context = System.Windows.Forms.WindowsFormsSynchronizationContext}
{where = 2}等待之后,currentTs = System.Threading.Tasks.ThreadPoolTask​​Scheduler,thread = 9,context = System.Windows.Forms.WindowsFormsSynchronizationContext}

问题是: 为什么是 TaskScheduler.Current 改变 SynchronizationContextTaskScheduler 至 ThreadPoolTaskScheduler 后 await 这里?

这基本上表现出这种行为 TaskCreationOptions.HideScheduler 对于 await 在我看来,延续,这是意想不到的和不可取的。

这个问题是由我的另一个问题引发的:

AspNetSynchronizationContext并等待ASP.NET中的延续


2902
2018-04-14 22:35


起源



答案:


如果没有实际的 任务 然后被执行 TaskScheduler.Current 是相同的 TaskScheduler.Default。换一种说法, ThreadPoolTaskScheduler 实际上,它们都充当线程池任务调度程序  该值表示“无当前任务调度程序”。

第一部分 async 委托是使用。明确安排的 SynchronizationContextTaskScheduler,并在UI线程上运行任务调度程序和同步上下文。任务调度程序将委托转发到同步上下文。

当。。。的时候 await 捕获其上下文,它捕获同步上下文(不是任务调度程序),并使用该syncctx恢复。因此,方法continuation被发布到syncctx,它在UI线程上执行它。

当延续在UI线程上运行时,它的行为与事件处理程序非常相似;委托是直接执行的,不包含在任务中。如果你检查 TaskScheduler.Current 在。。。之初 button1_Click,你会发现它也是 ThreadPoolTaskScheduler

顺便说一句,我建议您将此行为(直接执行委托,不包含在任务中)视为实现细节。


12
2018-04-15 00:11



这就是我认为它的工作方式,但不想在没有更多信息的情况下发表评论。 +1。 - Simon Whitehead
我明白了,显然这是怎么回事 TaskAwaiter 作品。 IMO,他们为“流动”提出的设计 TaskScheduler.Current 相当令人困惑:通常它不是你在逻辑上所期望的那样。 - Noseratio
同意; TaskScheduler.Current 在设计时考虑了动态并行性,因此子任务从父任务继承调度程序。这种默认行为让异步任务感到困惑,这就是我坚持在每个任务中明确指定调度程序的原因 StartNew 和 ContinueWith。 - Stephen Cleary


答案:


如果没有实际的 任务 然后被执行 TaskScheduler.Current 是相同的 TaskScheduler.Default。换一种说法, ThreadPoolTaskScheduler 实际上,它们都充当线程池任务调度程序  该值表示“无当前任务调度程序”。

第一部分 async 委托是使用。明确安排的 SynchronizationContextTaskScheduler,并在UI线程上运行任务调度程序和同步上下文。任务调度程序将委托转发到同步上下文。

当。。。的时候 await 捕获其上下文,它捕获同步上下文(不是任务调度程序),并使用该syncctx恢复。因此,方法continuation被发布到syncctx,它在UI线程上执行它。

当延续在UI线程上运行时,它的行为与事件处理程序非常相似;委托是直接执行的,不包含在任务中。如果你检查 TaskScheduler.Current 在。。。之初 button1_Click,你会发现它也是 ThreadPoolTaskScheduler

顺便说一句,我建议您将此行为(直接执行委托,不包含在任务中)视为实现细节。


12
2018-04-15 00:11



这就是我认为它的工作方式,但不想在没有更多信息的情况下发表评论。 +1。 - Simon Whitehead
我明白了,显然这是怎么回事 TaskAwaiter 作品。 IMO,他们为“流动”提出的设计 TaskScheduler.Current 相当令人困惑:通常它不是你在逻辑上所期望的那样。 - Noseratio
同意; TaskScheduler.Current 在设计时考虑了动态并行性,因此子任务从父任务继承调度程序。这种默认行为让异步任务感到困惑,这就是我坚持在每个任务中明确指定调度程序的原因 StartNew 和 ContinueWith。 - Stephen Cleary