线程可以做啥,而基于任务的异步模式(TAP)和任务并行(TPL)与任务(或任务<T>)不能做啥?

Posted

技术标签:

【中文标题】线程可以做啥,而基于任务的异步模式(TAP)和任务并行(TPL)与任务(或任务<T>)不能做啥?【英文标题】:What can Threads do that Task-based Asynchronous Pattern (TAP) and Task Parallelism (TPL) with Task (or Task<T>) cannot do?线程可以做什么,而基于任务的异步模式(TAP)和任务并行(TPL)与任务(或任务<T>)不能做什么? 【发布时间】:2021-11-12 21:14:33 【问题描述】:

在阅读 C# 中的多线程(来自 MSDocs 和 Stephen Cleary 的 Concurrency in C# 之类的书籍)时,我反复遇到的建议基本上归结为:线程是较旧的、低级别的抽象被TaskTask&lt;T&gt; 类取代的并发。

现在我了解到任务级别更高、功能更丰富、功能更强大,并且几乎可以执行线程以前用于异步和并行性的任何事情。

我的问题是:有什么线程可以做的,新的 TaskTask&lt;T&gt; 等不能做,所以我花时间学习多线程以防万一遇到这些用例?

【问题讨论】:

Task/Task&lt;T&gt; 涉及在多种情况下在幕后创建Thread。前者只是为后者精心设计的包装器,这使得正确使用多线程变得更加简单。如果您想真正了解一切是如何工作的,请从 Threads 开始,看看它们使用起来有多痛苦,然后切换到了解 Tasks、何时以及如何使用它们(尤其是在 async/@ 987654336@). 这非常正确,是的。您永远不应该在常见的 LOB 应用程序中需要线程。当你对线程的生命周期和其他方面有严格的要求时,你会回到线程。通常,您只能使用 asyncawait 构建完整的应用程序(正确完成,阅读 Stephen Cleary 的 *** 帖子和他的博客以获取更多信息) 如果我想更改 Thread.Priority 或处理器关联/IO 优先级(使用 pinvoke)或设置 AppartmentState,我会使用线程而不是任务或线程池线程。跨度> 标记为重复:Task vs Thread differences、What is the difference between task and thread?、when to use Task and when to use Thread?、Task vs Thread differences Is there anything that threads can do 它们允许您可靠地启动一个新线程。那有用吗?不常见,不。 【参考方案1】:

是的,您也需要了解线程。如果您对多线程一无所知,以下是您将无法做到的事情的非详尽列表:

    当这些任务彼此并行运行时,您将无法同步这些任务的操作。由于对locks、SemaphoreSlims、Mutexes、Barriers、Countdowns 等一无所知,您的并行和非同步任务将破坏应用程序的非线程安全状态。 您将无法通过使用 Interlocked 类对您的任务使用的变量和字段进行原子突变。 您将无法阻止编译器使用reordering the instructions of your program,从而导致您的任务遇到无效状态,因为您对内存屏障、volatile 关键字和Volatile 类一无所知。李> 您将无法启动在 STA 线程上运行的 Task。 您将无法启动在前台线程上运行的 Task。 您将无法启动在具有ThreadPriority 而非Normal 的线程上运行的Task。 您将无法利用高效的对象池,其中每个线程都可以使用自己的专用对象 (ThreadLocal&lt;T&gt;)。

关于学习多线程,这里有一个免费的在线资源:Joseph Albahari 的Threading in C#。

【讨论】:

谢谢。我接受这个答案就足够了,因为它实际上回答了所制定的问题——尽管这个列表上的东西都是相当低级的用例——我的预期场景(我在问题中没有提到)是关于业务线 (LOB) 应用程序 --- 可以由问题下方的 cmets 判断。再次感谢您的列表,尤其是指向 Albahari 作品的链接。 @explorer 是的,只要您不使用任务来引入并行性,并且您的应用程序中的所有内容都按顺序运行,那么在您的情况下,学习线程将是相当多的学术知识。 :-)【参考方案2】:

由于您提到的所有原因,任务都很好,它们可以重用池中的线程。这避免了拥有大量线程的开销(每个线程都需要一个堆栈,以及内核中的一些控制结构,跟踪它们等等),也避免了任务切换的开销——内核在线程之间转换需要一些周期.如果您有很多线程竞争同一个 CPU,那么您将花费更多时间进行切换,而花费更少的时间来做实际工作。

根据您的问题中的一个问题,直接使用线程意味着您可以控制生命周期,我唯一能想到的另一件事是线程本地存储 (https://docs.microsoft.com/en-us/dotnet/standard/threading/thread-local-storage-thread-relative-static-fields-and-data-slots)。

【讨论】:

以上是关于线程可以做啥,而基于任务的异步模式(TAP)和任务并行(TPL)与任务(或任务<T>)不能做啥?的主要内容,如果未能解决你的问题,请参考以下文章

基于任务的异步编程模式(TAP)的错误处理

基于任务的异步编程

基于任务的异步模式(TAP)

对话Task

实现基于任务的异步模式

基于任务的编程模型TAP