使用 Parallel.ForEach() 线程安全吗?

发布于 2024-10-31 14:27:32 字数 732 浏览 1 评论 0原文

本质上,我正在处理这个:

var data = input.AsParallel();
List<String> output = new List<String>();

Parallel.ForEach<String>(data, line => {
    String outputLine = ""; 
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI

    output.Add(outputLine);
});

输入是一个 List 对象。 ForEach() 语句对每个值进行一些处理,更新 UI,并将结果添加到 output List 中。这有什么本质上的错误吗?

注释:

  • 输出顺序不重要

更新:

根据我收到的反馈,我添加了手动锁定output.Add 语句,以及 UI 更新代码。

Essentially, I am working with this:

var data = input.AsParallel();
List<String> output = new List<String>();

Parallel.ForEach<String>(data, line => {
    String outputLine = ""; 
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI

    output.Add(outputLine);
});

Input is a List<String> object. The ForEach() statement does some processing on each value, updates the UI, and adds the result to the output List. Is there anything inherently wrong with this?

Notes:

  • Output order is unimportant

Update:

Based on feedback I've gotten, I've added a manual lock to the output.Add statement, as well as to the UI updating code.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

醉殇 2024-11-07 14:27:32

是的; List 不是线程安全的,因此从任意线程(很可能同时)向其添加临时内容是注定的。您应该使用线程安全列表,或者手动添加锁定。或者也许有一个Parallel.ToList

另外,如果重要的话:将不保证插入顺序。

不过,这个版本是安全的:

var output = new string[data.Count];

Parallel.ForEach<String>(data, (line,state,index) =>
{
    String outputLine = index.ToString();
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI
    output[index] = outputLine;
});

这里我们使用index来更新每个并行调用的不同数组索引。

Yes; List<T> is not thread safe, so adding to it ad-hoc from arbitrary threads (quite possibly at the same time) is doomed. You should use a thread-safe list instead, or add locking manually. Or maybe there is a Parallel.ToList.

Also, if it matters: insertion order will not be guaranteed.

This version is safe, though:

var output = new string[data.Count];

Parallel.ForEach<String>(data, (line,state,index) =>
{
    String outputLine = index.ToString();
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI
    output[index] = outputLine;
});

here we are using index to update a different array index per parallel call.

生生不灭 2024-11-07 14:27:32

这有什么本质上的错误吗?

是的,一切。这些都不安全。列表在多个线程上并发更新并不安全,并且您无法从 UI 线程以外的任何线程更新 UI。

Is there anything inherently wrong with this?

Yes, everything. None of this is safe. Lists are not safe for updating on multiple threads concurrently, and you can't update the UI from any thread other than the UI thread.

溇涏 2024-11-07 14:27:32

文档介绍了以下关于 List< 的线程安全性;T>:

此类型的公共静态(在 Visual Basic 中共享)成员是线程安全的。不保证任何实例成员都是线程安全的。

只要集合不被修改,List(Of T) 就可以同时支持多个读取器。通过集合进行枚举本质上不是线程安全的过程。在枚举与一个或多个写访问争用的极少数情况下,确保线程安全的唯一方法是在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。

因此,output.Add(outputLine) 不是线程安全的,您需要自己确保线程安全,例如,将添加操作包装在锁中声明。

The documentation says the following about the thread safety of List<T>:

Public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe.

A List(Of T) can support multiple readers concurrently, as long as the collection is not modified. Enumerating through a collection is intrinsically not a thread-safe procedure. In the rare case where an enumeration contends with one or more write accesses, the only way to ensure thread safety is to lock the collection during the entire enumeration. To allow the collection to be accessed by multiple threads for reading and writing, you must implement your own synchronization.

Thus, output.Add(outputLine) is not thread-safe and you need to ensure thread safety yourself, for example, by wrapping the add operation in a lock statement.

余生共白头 2024-11-07 14:27:32

当您想要并行操作的结果时,PLINQ< 更方便代码>并行类。您一开始就将 input 转换为 ParallelQuery:

ParallelQuery<string> data = input.AsParallel();

...但是随后您将 data 提供给 Parallel.ForEach,这将其视为标准 IEnumerable。所以 AsParallel() 被浪费了。它不提供任何并行化,仅提供开销。以下是使用 PLINQ 的正确方法:

List<string> output = input
    .AsParallel()
    .Select(line =>
    {
        string outputLine = ""; 
        // ** Do something with "line" and store result in "outputLine" **
        return outputLine;
    })
    .ToList();

您应该记住的一些差异:

  1. ParallelThreadPool 默认情况下,但它是 可配置。 PLINQ 专门使用ThreadPool
  2. 默认情况下,Parallel 具有无限的并行性(它使用ThreadPool 的所有可用线程)。默认情况下,PLINQ 最多使用 Environment.ProcessorCount< /code>线程。

关于结果的顺序,PLINQ 默认情况下不保留顺序。如果您想保留顺序,可以附加 AsOrdered 运算符。

When you want the results of a parallel operation, the PLINQ is more convenient than the Parallel class. You started well by converting your input to a ParallelQuery<T>:

ParallelQuery<string> data = input.AsParallel();

...but then you fed the data to the Parallel.ForEach, which treats it as a standard IEnumerable<T>. So the AsParallel() was wasted. It didn't provide any parallelization, only overhead. Here is the correct way to use PLINQ:

List<string> output = input
    .AsParallel()
    .Select(line =>
    {
        string outputLine = ""; 
        // ** Do something with "line" and store result in "outputLine" **
        return outputLine;
    })
    .ToList();

A few differences that you should have in mind:

  1. The Parallel runs the code on the ThreadPool by default, but it's configurable. The PLINQ uses exclusively the ThreadPool.
  2. The Parallel by default has unlimited parallelism (it uses all the available threads of the ThreadPool). The PLINQ uses by default at most Environment.ProcessorCount threads.

Regarding the order of the results, PLINQ doesn't preserve the order by default. In case you want to preserve the order, you can attach the AsOrdered operator.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文