代理是一些使.NET reference中的线程更容易的对象。它们可用于异步调用方法。框架4.5(或更早版本)中存在哪些其他对象使线程的使用更容易或更不容易出错?
其他抽象有哪些使并发和多线程更容易?
注意:此问题会更新this。
答案 0 :(得分:21)
我倾向于回答很多与多线程有关的问题,我经常看到以不同方式提出的相同基本问题。我将介绍我多年来看到的最常见的问题,并解释新技术如何使这些问题更容易解决。
关闭循环变量
这不是特定于线程的问题,但使用线程肯定会放大问题。 C#5.0通过为每次迭代创建一个新变量来修复foreach
循环的这个问题。您将不再需要为lambda表达式闭包创建特殊变量。不幸的是,for
循环仍然需要使用特殊的捕获变量来处理。
等待异步任务完成
.NET 4.0引入了CountdownEvent
类,它封装了许多等待完成许多任务所需的逻辑。大多数初级开发人员使用Thread.Join
个电话或单个WaitHandle.WaitAll
电话。这两者都存在可扩展性问题。旧模式是使用单个ManualResetEvent
并在计数器达到零时发出信号。计数器已使用Interlocked
类进行更新。 CountdownEvent
使这种模式更容易。只要记住将你的主人当作一名工人,以避免如果一名工人在所有工人排队之前完成就会发生微妙的竞争状况。
.NET 4.0还引入了Task
类,它可以通过TaskCreationOptions.AttachedToParent
将子任务链接起来。如果您在父母上致电Task.Wait
,它也会等待所有子任务完成。
<强>生产者 - 消费者强>
.NET 4.0引入了BlockingCollection
类,它的行为类似于普通队列,但它可以在集合为空时阻塞。您可以通过调用Add
对对象进行排队,并通过调用Take
使对象出列。 Take
阻止,直到某个项目可用。这大大简化了生产者 - 消费者逻辑。曾经是开发人员试图编写自己的阻塞队列类的情况。但是,如果你不知道自己在做什么,那么你真的可以搞砸了......糟糕。实际上,微软在MSDN文档中有一个阻塞队列示例的时间最长,这本身就是严重破坏的。幸运的是,它已被删除。
使用工作线程进度更新UI
BackgroundWorker
的引入使新手开发人员更容易从WinForm应用程序中脱离后台任务。主要好处是您可以从ReportProgress
事件处理程序中调用DoWork
,ProgressChanged
事件处理程序将自动封送到UI线程中。当然,任何跟踪我在SO上的答案的人都知道我对编组操作的感受(通过Invoke
等)作为用简单的进度信息更新UI的解决方案。我总是扯掉它,因为它通常是一种可怕的方法。 BackgroundWorker
仍然迫使开发人员进入推送模型(通过后台编组操作),但至少它会在幕后完成所有这些。
调用的不雅
我们都知道只能从UI线程访问UI元素。这通常意味着开发人员必须通过ISynchronizeInvoke
,DispatcherObject
或SynchronizationContext
使用编组操作来将控制权转移回UI线程。但是让我们面对现实。这些编组操作看起来很丑陋。 Task.ContinueWith
使这更加优雅,但真正的荣耀归于await
,作为C#5的新异步编程模型的一部分。 await
可用于等待Task
完成,以便在任务运行时临时中断流控制,然后在右同步上下文中的该位置返回。没有什么比使用await
替代所有Invoke
次来电更优雅和满意了。
并行编程
我经常看到问题是如何并行发生的。旧方法是创建几个线程或使用ThreadPool
。 .NET 4.0使用了TPL和PLINQ。 Parallel
类是使循环的迭代并行进行的好方法。对于普通的旧LINQ,PLINQ的AsParallel
是同一枚硬币的另一面。这些新的TPL功能大大简化了这类多线程编程。
.NET 4.5引入了TPL数据流库。它旨在使复杂的并行编程问题变得优雅。它将类抽象为块。它们可以是目标块或源块。数据可以从一个块流到另一个块。有许多不同的块,包括BufferBlock<T>
,BroadcastBlock<T>
,ActionBlock<T>
等,它们都做不同的事情。当然,整个库将进行优化,以便与新的async
和await
关键字一起使用。这是一组令人兴奋的新课程,我认为这些课程会慢慢流行起来。
优雅终止
如何让线程停止?我经常看到这个问题。最简单的方法是致电Thread.Abort
,但我们都知道这样做的危险......我希望如此。有许多不同的方法可以安全地完成这项工作。 .NET 4.0引入了一个更加统一的概念,称为通过CancellationToken
和CancellationTokenSource
取消。后台任务可以轮询IsCancellationRequested
或仅在安全点调用ThrowIfCancellationRequested
以优雅地中断他们正在做的任何工作。其他线程可以调用Cancel
来请求取消。
答案 1 :(得分:7)
嗯,让我们来看看:
ThreadPool
类 - 有点旧,但对于简单的生产者 - 消费者模式仍然可靠。BackgoundWorker
(.NET 2.0+) - 另一种老式构造,为在GUI应用程序中在后台执行任务提供了有用的功能。Timer
s - 用于使用后台线程以指定的时间间隔执行代码。Task
类(.NET 4.0+) - 在底层线程池上运行的线程抽象,并提供许多有用的功能,如异常编组和调度。对于所谓的&#34;任务并行性有用#34;图案。Parallel.For
,Parallel.ForEach
(.NET 4.0+) - 适合在一组数据上并行执行相同的操作。对于所谓的&#34;数据并行&#34;非常有用。图案。Parallel.Invoke
(.NET 4.0+) - 对Task
的进一步抽象。只需并行激活几段代码(方法,lambdas)。答案 2 :(得分:2)
毫无疑问,掌握新的Tpl DataFlow库(包含在.net 4.5中)将为您提供并发开发方面的最大推动力。
如果您对高度并发的应用程序非常认真,请花一两天时间熟悉DataFlow。这非常好。
答案 3 :(得分:1)
Task
和Task<T>
,但它们自.NET 4以来一直在这里。async
不一定适用于线程,请参阅Jon's video from Øredev以获得非常好的解释