Stefano d'Antonio的精彩答案详细描述了RunSynchronously
方法的机制。我想添加一些实际例子,说明RunSynchronously
实际上很有用。
第一个例子:并行化两个方法调用,同时有效地利用线程:
Parallel.Invoke(() => Method1(), () => Method2());
一个天真的假设可能是
Method1
和
Method2
将在两个不同的
ThreadPool
线程上运行,但实际情况并非如此。在幕后,
Parallel.Invoke
将在当前线程上调用其中一个方法。上面的代码与下面的代码具有相同的行为:
Task task1 = Task.Run(() => Method1());
Task task2 = new(() => Method2());
task2.RunSynchronously(TaskScheduler.Default);
Task.WaitAll(task1, task2);
当当前线程可用时,没有理由将两个调用都卸载到ThreadPool
中,因为当前线程可以愉快地参与并行执行。
第二个例子:创建一个代表尚未启动的某些代码的Task
表示形式,以便其他异步方法可以await
此任务并获取其结果:
Task<DateOnly> task1 = new(() => CalculateDate());
Task<string> task2 = Task.Run(() => GenerateReportAsync(task1));
task1.RunSynchronously(TaskScheduler.Default);
string report = await task2;
我们现在有两个任务同时运行,task1
和task2
,第二个任务依赖于第一个任务的结果。因为没有必要将CalculateDate
委派给另一个线程,所以task1
在当前线程上同步运行。当前线程可以像任何其他线程一样进行计算。在GenerateReportAsync
内部,第一个任务在某个地方被等待,可能会多次等待:
async Task<string> GenerateReportAsync(Task<DateOnly> dateTask)
{
DateOnly date = await dateTask;
}
await dateTask
可能会立即获得结果(如果此时 dateTask
已经完成),或异步地获得结果(如果 dateTask
还在运行)。无论哪种情况,我们都已经达到了我们想要的目的:将日期的计算并行化,以便生成报告方法中的 // Do preliminary stuff
部分。
我们能否在不使用 Task
构造函数和 RunSynchronously
方法的情况下复制这两个示例的行为?当然可以,使用 TaskCompletionSource
类。但是,不像使用上述技术那样简洁、稳健和描述性。
Run
或RunSynchronously
是没有用处的,因为它们返回“热”任务(即已经在运行)。你只能在“冷”任务上使用Run
或RunSynchronously
,这些任务还没有开始。 - Damien_The_Unbelieverasync await
流程之前,已经创建了RunSynchronously
方法。显然,这个想法是允许使用相同的线程上下文和指定的调度程序同步执行“冷”任务。与.StartNew()
一样,它已经过时了。除非您需要在当前线程中创建一个冷任务并同步运行它,否则没有使用它的意义,使用当前的TaskScheduler
。 - Fabjan