欢迎Mono / Xamarin特定的答案。
我正在使用Task.Run()运行System.Threading.Tasks。 TPL是否会在任务执行期间将创建的任务分配给单个线程?或者创建的任务是否可能在运行时被抢占,然后再次在另一个线程上安排?
Thread.CurrentThread.ManagedThreadId会在任务的生命周期内保持不变吗?
对于长期运行的任务,答案是否有所不同?
有没有办法控制这方面的TPL行为?
答案 0 :(得分:4)
Task.Run
将调度委托在线程池中执行,并且线程池将只在单个线程上执行给定的委托。它不会在线程之间移动它。
也就是说,Task
在概念上仅仅是在未来某个时刻完成某些异步操作的表示。它可以表示最终完成的任何。如果你想创建一个代表一个线程上一个委托的执行的任务,然后在另一个线程上执行另一个委托,那么你绝对可以做到这一点。
使用async
关键字的许多方法实际上都是这样做的。因为,在没有设置同步上下文的应用程序中,作为await
调用的结果连接的连续可能(它们有时可以被优化)每个在线程池中单独调度。例如,在控制台应用程序中运行的以下代码生成的任务可能会打印出三个完全不同的数字。 (当然,它们不是必需的是不同的;线程池可能正好安排在同一个线程上运行的延续。)
public static async Task Foo()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(100);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
await Task.Delay(100);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
答案 1 :(得分:4)
TPL会在任务执行期间将创建的任务分配给单个线程吗?
指南是同步部分工作在单个线程上运行。因此,如果将同步委托传递给Task.Run
,它将全部在单个线程上运行:
await Task.Run(() => Thread.Sleep(5000)); // same thread after sleep
但是,如果您有异步代码,则每个await
都是代码中挂起方法的位置。当方法恢复时,它将在线程池线程(可能是不同的线程)上恢复。
await Task.Run(async () => await Task.Delay(5000)); // thread may change
长时间运行的标志(无法传递给Task.Run
)不会影响此行为,因为它仅适用于第一个同步部分。
控制此操作的常用方法是使用自定义TaskScheduler
或SynchronizationContext
。但是,在走这条路之前,您可能想要考虑另一种方法。应该有一个更好的解决方案,而不是强迫该方法回到同一个线程。例如,线程仿射同步原语都具有async
- 兼容的等价物,线程局部存储可以被闭包/类字段/逻辑调用上下文等替换。