了解 TaskScheduler.Current 的行为 [英] Understanding the behavior of 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.ThreadPoolTaskScheduler,thread = 9,context = System.Windows.Forms.WindowsFormsSynchronizationContext }问题: 为什么 TaskScheduler.Current
在 await<之后从
SynchronizationContextTaskScheduler
变为 ThreadPoolTaskScheduler
/code> 在这里?
这基本上展示了 await
继续的行为 TaskCreationOptions.HideScheduler
,在我看来,这是出乎意料和不可取的.
这个问题是由我的另一个问题引发的:
AspNetSynchronizationContext 并在 ASP.NET 中等待继续.
如果没有实际的task被执行,那么TaskScheduler.Current
和 是一样的>TaskScheduler.Default
.换句话说,ThreadPoolTaskScheduler
实际上既充当线程池任务调度器又表示没有当前任务调度器"的值.
async
委托的第一部分使用 SynchronizationContextTaskScheduler
显式调度,并在 UI 线程上运行,同时具有任务调度程序和同步上下文.任务调度器将委托转发到同步上下文.
当 await
捕获其上下文时,它会捕获同步上下文(而不是任务调度程序),并使用该 syncctx 来恢复.所以,方法延续被发布到那个syncctx,它在UI线程上执行它.
当延续在 UI 线程上运行时,它的行为与事件处理程序非常相似;委托是直接执行的,而不是包装在任务中.如果你检查button1_Click
开头的TaskScheduler.Current
,你会发现它也是ThreadPoolTaskScheduler
.
顺便说一句,我建议您将此行为(直接执行委托,而不是包含在任务中)视为实现细节.
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屋!