理解TaskScheduler.Current的行为

14

这是一个简单的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) 在 await 之后,currentTs = System.Threading.Tasks.ThreadPoolTaskScheduler,thread = 9,context = System.Windows.Forms.WindowsFormsSynchronizationContext }

问题:为什么TaskScheduler.Current在这里await之后从SynchronizationContextTaskScheduler更改为ThreadPoolTaskScheduler

这基本上展示了TaskCreationOptions.HideScheduler用于await继续的行为,我认为这是意外和不希望的。

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

AspNetSynchronizationContext and await continuations in ASP.NET.

1个回答

16
如果没有实际正在执行的任务,则TaskScheduler.CurrentTaskScheduler.Default相同。换句话说,ThreadPoolTaskScheduler实际上既充当线程池任务调度程序,又充当“没有当前任务调度程序”的值。

async委托的第一部分使用SynchronizationContextTaskScheduler显式地进行调度,并在具有任务调度程序和同步上下文的UI线程上运行。任务调度程序将委托转发到同步上下文。

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

当续订在UI线程上运行时,它的行为非常类似于事件处理程序;委托直接执行,而不是包装在任务中。如果在button1_Click开始时检查TaskScheduler.Current,您会发现它也是ThreadPoolTaskScheduler

顺便说一句,我建议您将这种行为(直接执行委托而不是包装在任务中)视为实施细节。


这是我认为它的工作方式,但是没有更多信息就不想发表评论。+1。 - Simon Whitehead
我明白了,显然这就是 TaskAwaiter 的工作原理。在我看来,他们为“流动”TaskScheduler.Current设计的方式相当令人困惑:通常它不是你逻辑上期望的那样。 - noseratio - open to work
4
同意;TaskScheduler.Current 被设计用于动态并行性,使得子任务可以从父任务继承调度器。这种默认行为对于异步任务来说是令人困惑的,因此我坚持在每个 StartNewContinueWith 中明确指定调度器。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接