C# TPL能否在多个线程上运行任务?

C# TPL能否在多个线程上运行任务?,c#,multithreading,mono,xamarin.ios,xamarin,C#,Multithreading,Mono,Xamarin.ios,Xamarin,欢迎单体/沙马林特定答案 我正在使用Task.Run()运行System.Threading.Tasks。TPL会在任务的执行生命周期中将创建的任务分配给单个线程吗?或者,创建的任务是否可能在运行时被抢占,然后在另一个线程上再次调度 Thread.CurrentThread.ManagedThreadId在任务生命周期内是否保持不变 对于长时间运行的任务,答案是否不同 在这方面是否有控制TPL行为的方法?任务。Run将计划在线程池中执行委托,而线程池将仅在单个线程上执行给定委托。它不会在线程之间

欢迎单体/沙马林特定答案

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

Thread.CurrentThread.ManagedThreadId在任务生命周期内是否保持不变

对于长时间运行的任务,答案是否不同


在这方面是否有控制TPL行为的方法?

任务。Run
将计划在线程池中执行委托,而线程池将仅在单个线程上执行给定委托。它不会在线程之间移动它

这就是说,
任务
在概念上仅仅是在将来某个时刻完成某个异步操作的表示。它可以代表最终完成的任何事情。如果您想创建一个任务,该任务表示一个委托在一个线程上的执行,然后另一个委托在另一个线程上的执行,那么您完全可以这样做

许多使用
async
关键字的方法实际上都会这样做。因为,在没有设置同步上下文的应用程序中,由于
wait
调用而连接的continuations可以(有时可以优化)在线程池中单独调度。例如,以下在控制台应用程序中运行的代码生成一个任务,该任务可能打印出三个完全不同的数字。(当然,它们不需要不同;线程池可能恰好将continuations安排在同一个线程上运行。)

公共静态异步任务Foo()
{
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
等待任务。延迟(100);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
等待任务。延迟(100);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
}
TPL会在任务的执行生命周期中将创建的任务分配给单个线程吗

指导原则是工作的同步部分在单个线程上运行。因此,如果将同步委托传递给
Task.Run
,它将在单个线程上运行:

await Task.Run(() => Thread.Sleep(5000)); // same thread after sleep
但是,如果您有异步代码,则每个
wait
都是代码中挂起方法的位置。当方法恢复时,它将在线程池线程(可能是不同的线程)上恢复

长时间运行标志(不能传递给
任务.Run
)不会影响此行为,因为它仅适用于第一个同步部分


控制这一点的正常方法是使用自定义的
TaskScheduler
SynchronizationContext
。然而,在走那条路之前,你可能想考虑另一种方法。应该有一个更好的解决方案,而不是将方法强制回到同一个线程上。例如,线程仿射同步原语都有
async
-兼容的等价物,线程本地存储可以被闭包/类字段/逻辑调用上下文等替换。

您如何确定地知道这一点:“它不会在线程之间移动。”?首先,它不可能真的移动。当你告诉一个线程去执行一个委托时,没有一种机制可以只执行它的一半然后停止,而不需要合作。逻辑C#线程可能无法与操作系统线程进行1:1映射(尽管我非常确信这不会真正发生),但这超出了抽象层的范围,无论如何,您都需要了解。如果它能改变执行代码的线程(它不能),那么它就不会破坏你的程序。如果是这样的话,你几乎肯定会做一些你不应该做的事情。所以,基本上,你并不确定(没关系)。我不知道TPL的实现,但我可以想象一个长时间运行的任务可以被抢占,然后由于线程数量有限而继续。我可能猜TPL可以选择不同的线程继续执行任务。至于你在不了解任何背景的情况下说出“你几乎肯定会做一些你不应该做的事情”的能力,哥们,这太神奇了。你应该找到一种方法来赚钱。@jeff7091我知道线程池不会在多个线程上执行给定的操作。线程一直会暂停并稍后继续,但它们不会暂停然后执行另一个线程的状态。是的,即使不知道上下文,我也可以确信,如果您在多线程环境中编写代码,并期望它在特定线程上执行,很有可能你正在做一些不应该做的事情,因为使用这些抽象层的全部目的就是抽象掉这些概念。@jeff7091我理解你的想法。TPL不会跨线程移动正在运行的代码这一事实很可能不存在。理论上,它可以通过运行时的合作做到这一点。另一方面:a)几乎不需要这样做(你不同意吗?)b)很难实现c)用户代码可以检测到这一点,因为ManagedThreadId可以随时更改d)非托管代码无法处理这一点,并且.NET一直调用非托管代码。=>TPL没有这样做。我不能向你提供官方消息来源。来源:Servy和我。值得注意的是,从技术上讲,在您的第二个示例中,
Run
仍在执行它在单个线程上接收的整个委托。您传递给
Run
的委托明确定义了它的行为,即做某事,然后安排在另一个线程池线程中执行操作,因此说
await Task.Run(async () => await Task.Delay(5000)); // thread may change