了解 TaskScheduler.Current 的行为 [英] Understanding the behavior of TaskScheduler.Current

查看:30
本文介绍了了解 TaskScheduler.Current 的行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

使用系统;使用 System.Diagnostics;使用 System.Threading;使用 System.Threading.Tasks;使用 System.Windows.Forms;命名空间 WindowsFormsApplication{公共部分类 Form1 :表单{公共 Form1(){初始化组件();}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.CurrentThread.ManagedThreadId,上下文 = SynchronizationContext.Current});等待 Task.Yield();//或等待 Task.Delay(1)Debug.WriteLine(new{where = "2) after await",currentTs = TaskScheduler.Current,线程 = Thread.CurrentThread.ManagedThreadId,上下文 = SynchronizationContext.Current});}, CancellationToken.None, TaskCreationOptions.None, 调度程序: ts).Unwrap();}}}

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

<前>{ where = 1) before 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.Currentawait<之后从 SynchronizationContextTaskScheduler 变为 ThreadPoolTask​​Scheduler/code> 在这里?

这基本上展示了 await 继续的行为 TaskCreationOptions.HideScheduler,在我看来,这是出乎意料和不可取的.

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

AspNetSynchronizationContext 并在 ASP.NET 中等待继续.

解决方案

如果没有实际的task被执行,那么TaskScheduler.Current 是一样的>TaskScheduler.Default.换句话说,ThreadPoolTask​​Scheduler 实际上既充当线程池任务调度器表示没有当前任务调度器"的值.

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

await 捕获其上下文时,它会捕获同步上下文(而不是任务调度程序),并使用该 syncctx 来恢复.所以,方法延续被发布到那个syncctx,它在UI线程上执行它.

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

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

Here's a simple WinForms app:

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

The debug ouput (when the button is clicked):

{ where = 1) before await, currentTs = System.Threading.Tasks.SynchronizationContextTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext }
{ where = 2) after await, currentTs = System.Threading.Tasks.ThreadPoolTaskScheduler, thread = 9, context = System.Windows.Forms.WindowsFormsSynchronizationContext }

The question: Why is TaskScheduler.Current changing from SynchronizationContextTaskScheduler to ThreadPoolTaskScheduler after await here?

This essentially exhibits the behavior TaskCreationOptions.HideScheduler for await continuation, which is unexpected and undesirable, in my opinion.

This question has been triggered by another question of mine:

AspNetSynchronizationContext and await continuations in ASP.NET.

解决方案

If there is no actual task being executed, then TaskScheduler.Current is the same as TaskScheduler.Default. In other words, ThreadPoolTaskScheduler actually acts both as the thread pool task scheduler and the value meaning "no current task scheduler".

The first part of the async delegate is scheduled explicitly using the SynchronizationContextTaskScheduler, and runs on the UI thread with both a task scheduler and synchronization context. The task scheduler forwards the delegate to the synchronization context.

When the await captures its context, it captures the synchronization context (not the task scheduler), and uses that syncctx to resume. So, the method continuation is posted to that syncctx, which executes it on the UI thread.

When the continuation runs on the UI thread, it behaves very similarly to an event handler; the delegate is executed directly, not wrapped in a task. If you check TaskScheduler.Current at the beginning of button1_Click, you'll find it is also ThreadPoolTaskScheduler.

BTW, I recommend you treat this behavior (executing delegates directly, not wrapped in tasks) as an implementation detail.

这篇关于了解 TaskScheduler.Current 的行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆