12

委托是使 .NET引用中的线程更容易的一些对象。它们可用于异步调用方法。框架 4.5(或更早版本)中还有哪些其他对象可以使线程的使用更容易或更不容易出错?

还有哪些其他抽象使并发和多线程更容易?

注意:这个问题更新了这个

4

4 回答 4

21

我倾向于回答很多与多线程相关的问题,而且我经常看到以各种不同方式提出的相同基本问题。我将介绍多年来我看到的最常见的问题,并解释新技术如何使解决这些问题变得更容易。

关闭循环变量

这不是线程特有的问题,但使用线程肯定会放大问题。foreachC# 5.0通过为每次迭代创建一个新变量来解决循环的这个问题。您将不再需要为 lambda 表达式闭包创建特殊变量。不幸的是,for循环仍然需要使用特殊的捕获变量来处理。

等待异步任务完成

.NET 4.0 引入了一个CountdownEvent类,它封装了等待许多任务完成所需的大量逻辑。大多数初级开发人员使用Thread.Join调用或单个WaitHandle.WaitAll调用。这两者都存在可扩展性问题。旧模式是使用一个单一的ManualResetEvent并在计数器达到零时发出信号。使用Interlocked该类更新了计数器。CountdownEvent使这种模式更容易。只需记住将您的 main 也视为工作人员,以避免如果一名工作人员在所有工作人员排队之前完成,可能发生的微妙竞争条件。

.NET 4.0 还引入了Task可以通过TaskCreationOptions.AttachedToParent. 如果您调用Task.Wait父母,它也会等待所有子任务完成。

生产者-消费者

.NET 4.0 引入了BlockingCollection类似于普通队列的类,除了它可以在集合为空时阻塞。您可以通过调用对对象进行排队,Add并通过调用将对象出列TakeTake阻塞,直到一个项目可用。这大大简化了生产者-消费者逻辑。过去,开发人员试图编写自己的阻塞队列类。但是,如果你不知道自己在做什么,那么你真的可以把它搞砸……糟糕。事实上,很长一段时间以来,微软在 MSDN 文档中都有一个阻塞队列示例,该示例本身就被严重破坏了。幸运的是,它已被删除。

使用工作线程进度更新 UI

BackgroundWorker对于新手开发人员来说,从 WinForm 应用程序中分离后台任务的引入变得更加容易。主要好处是您可以ReportProgressDoWork事件处理程序中调用,并且ProgressChanged事件处理程序将自动编组到 UI 线程。当然,任何在 SO 上跟踪我的答案的人都知道我对编组操作(通过Invoke等)作为使用简单进度信息更新 UI 的解决方案的感受。我一直在嘲笑它,因为它通常是一种糟糕的方法。BackgroundWorker仍然迫使开发人员进入推送模型(通过后台的编组操作),但至少它在幕后完成了所有这些工作。

Invoke 的不优雅

我们都知道一个 UI 元素只能从 UI 线程访问。这通常意味着开发人员必须通过 、 或 使用编组操作ISynchronizeInvokeDispatcherObject或者SynchronizationContext将控制权转移回 UI 线程。但是让我们面对它。这些编组操作看起来很难看。Task.ContinueWith使它更优雅一点,但真正的荣耀await属于 C# 5 的新异步编程模型的一部分。await可用于等待 aTask完成,即流控制在任务运行时暂时中断,然后在正确的同步上下文中的那个位置返回。没有什么比await用作所有这些Invoke调用的替代品更优雅和令人满意的了。

并行编程

我经常看到一些问题,询问事情如何并行发生。旧方法是创建几个线程或使用ThreadPool. .NET 4.0 使用了 TPL 和 PLINQ。该类Parallel是使循环的迭代并行进行的好方法。对于普通的旧 LINQ ,PLINQAsParallel是同一枚硬币的另一面。这些新的 TPL 特性极大地简化了这类多线程编程。

.NET 4.5 引入了 TPL 数据流库。它旨在使原本复杂的并行编程问题变得优雅。它将类抽象为块。它们可以是目标块或源块。数据可以从一个块流向另一个块。有许多不同的块,包括BufferBlock<T>BroadcastBlock<T>ActionBlock<T>等,它们都做不同的事情。而且,当然,整个库将针对使用 newasyncawait关键字进行优化。这是一组令人兴奋的新课程,我认为它会慢慢流行起来。

优雅终止

你如何让线程停止?我经常看到这个问题。最简单的方法是打电话Thread.Abort,但我们都知道这样做的危险......我希望。有许多不同的方法可以安全地做到这一点。CancellationToken.NET 4.0 引入了一个更统一的概念,称为通过和取消CancellationTokenSource。后台任务可以轮询IsCancellationRequested或只是ThrowIfCancellationRequested在安全点调用以优雅地中断他们正在做的任何工作。其他线程可以调用Cancel以请求取消。

于 2012-05-10T18:35:28.307 回答
7

好吧,让我们在这里看看:

  1. 该类ThreadPool- 有点旧,但对于简单的生产者 - 消费者模式仍然可靠。
  2. BackgoundWorker(.NET 2.0+) - 另一个老式结构,为在 GUI 应用程序的后台执行任务提供有用的功能。
  3. Timers - 用于使用后台线程以指定的时间间隔执行代码。
  4. 该类Task(.NET 4.0+) - 在底层线程池上运行的线程抽象,并提供许多有用的功能,如异常封送处理和调度。对于所谓的“任务并行”模式很有用。
  5. Parallel.For, Parallel.ForEach(.NET 4.0+) - 适合对一组数据并行执行相同的操作。对于所谓的“数据并行”模式很有用。
  6. Parallel.Invoke(.NET 4.0+) - 对Tasks 的进一步抽象。只需并行触发几段代码(方法、lambda)。
  7. 并发集合 (.NET 4.0+) - 以高效和线程安全的方式在线程之间传递或共享数据所需的一切。
于 2012-05-10T14:37:06.563 回答
2

毫无疑问,掌握新的Tpl DataFlow库(包含在 .net 4.5 中)将为您在并发开发方面提供最大的推动力。

如果您认真对待高并发应用程序,请花一两天时间熟悉 DataFlow。真的很好。

于 2012-05-10T12:40:00.880 回答
1

TaskTask<T>,但它们从 .NET 4 开始就一直存在。不一定async适用于线程,请参阅Øredev的 Jon 的视频以获得很好的解释。

于 2012-05-10T12:35:12.303 回答