无法将丰富的异步功能嵌入到自定义TaskScheduler
. 这个类的设计没有考虑到async
/ await
。使用自定义的标准方法TaskScheduler
是作为Task.Factory.StartNew
方法的参数。此方法不理解异步委托。可以提供异步委托,但它被视为返回某些结果的任何其他委托。要获得异步委托的实际等待结果,必须调用Unwrap()
返回的任务。
不过,这不是问题。问题是TaskScheduler
基础架构没有将异步委托视为单个工作单元。每个任务被分成多个小任务(使用everyawait
作为分隔符),每个小任务单独处理。这严重限制了可以在此类之上实现的异步功能。例如,这里有一个自定义TaskScheduler
,旨在一次将提供的任务排队(换句话说,限制并发):
public class MyTaskScheduler : TaskScheduler
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1);
protected async override void QueueTask(Task task)
{
await _semaphore.WaitAsync();
try
{
await Task.Run(() => base.TryExecuteTask(task));
await task;
}
finally
{
_semaphore.Release();
}
}
protected override bool TryExecuteTaskInline(Task task,
bool taskWasPreviouslyQueued) => false;
protected override IEnumerable<Task> GetScheduledTasks() { yield break; }
}
SemaphoreSlim
应该确保一次只Task
运行一个。不幸的是,它不起作用。信号量过早释放,因为Task
调用中传入的QueueTask(task)
不是代表异步委托的全部工作的任务,而只是直到第一个await
. 其他部分传递给该TryExecuteTaskInline
方法。没有办法关联这些任务部分,因为没有提供标识符或其他机制。以下是实践中发生的情况:
var taskScheduler = new MyTaskScheduler();
var tasks = Enumerable.Range(1, 5).Select(n => Task.Factory.StartNew(async () =>
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Started");
await Task.Delay(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.fff} Item {n} Finished");
}, default, TaskCreationOptions.None, taskScheduler))
.Select(t => t.Unwrap())
.ToArray();
Task.WaitAll(tasks);
输出:
05:29:58.346 项目 1 开始
05:29:58.358 项目 2 开始
05:29:58.358 项目 3 开始
05:29:58.358 项目 4 开始
05:29:58.358 项目 5 开始
05:29:59.358 项目 1 完成
05: 29:59.374 第 5 项已完成
05:29:59.374 第 4 项已完成
05:29:59.374 第 2 项已完成
05:29:59.374 第 3 项已完成
灾难,所有任务一次排队。
结论:当需要高级异步功能时,自定义TaskScheduler
类不是要走的路。
更新:这是另一个观察,关于TaskScheduler
在环境存在的custom SynchronizationContext
。默认情况下,该await
机制捕获 currentSynchronizationContext
或 current TaskScheduler
,并在捕获的上下文或调度程序上调用延续。如果两者都存在,SynchronizationContext
则首选当前,而TaskScheduler
忽略当前。下面是此行为的演示,在 WinForms 应用程序中¹:
private async void Button1_Click(object sender, EventArgs e)
{
await Task.Factory.StartNew(async () =>
{
MessageBox.Show($"{Thread.CurrentThread.ManagedThreadId}, {TaskScheduler.Current}");
await Task.Delay(1000);
MessageBox.Show($"{Thread.CurrentThread.ManagedThreadId}, {TaskScheduler.Current}");
}, default, TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
}
单击该按钮会依次弹出两条消息,其中包含以下信息:
1、System.Threading.Tasks.SynchronizationContextTaskScheduler
1、System.Threading.Tasks.ThreadPoolTaskScheduler
这个实验表明,只有异步委托的第一部分,即第一部分之前的部分await
,被安排在非默认调度程序上。这种行为甚至进一步限制了 customTaskScheduler
在启用 async/await 的环境中的实际用途。
¹ Windows Forms 应用程序在调用该方法WindowsFormsSynchronizationContext
时会自动安装。Application.Run