TPL可以在多个线程上运行任务吗?

Mono / Xamarin特定的答案受到欢迎。

我正在使用Task.Run()运行System.Threading.Tasks。 TPL是否会在任务执行期间将创建的任务分配给单个线程? 或者创建的任务是否可能在运行时被抢占,然后再次在另一个线程上安排?

Thread.CurrentThread.ManagedThreadId会在任务的生命周期内保持不变吗?

对于长期运行的任务,答案是否不同?

有没有办法控制这方面的TPL行为?

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方法是使用自定义TaskSchedulerSynchronizationContext 。 但是,在走这条路之前,您可能想要考虑另一种方法。 应该有一个比将该方法强制回到同一个线程更好的解决方案。 例如,线程仿射同步原语都具有async兼容的等价物,线程局部存储可以由闭包/类字段/逻辑调用上下文等替换。

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