使用具有返回结果的操作列表的 Parallel.ForEach

huangapple go评论73阅读模式
英文:

Parallel.ForEach with a list of actions returning results

问题

我有一个调用不同方法的操作列表。所有操作都需要在不同的线程中并行运行,使用Parallel.ForEach。所有操作将返回一个包含一堆信息的对象Result。所有的结果都将使用对日志的锁添加到日志中。

我的问题和可能的问题是:这些操作方法可以有一个返回值,以便在Parallel.ForEach中检索和使用,应该如何实现?另外,如何安全地使用Result,而不会在一个线程完成执行时被另一个结果等待日志更新的情况下被覆盖?

我的当前简化代码大致如下:

private Log _log;

private void DoParallelWork()
{
    List<Action> actions = new List<Action> { Action1, Action2, Action3, ...etc };
    Result result = new Result();

    System.Threading.Tasks.Parallel.ForEach(actions , action =>
    {
        result = action(); // 我怎样才能把结果传回ForEach?

        lock (_log)
        {
            UpdateLog(result);
        }
    });
}

private Result Action1() // Action2、3等具有相同结构
{
    return [调用数据库执行存储过程返回Result对象的代码];
}

private void UpdateLog(Result result)
{
    // 根据结果更新 _log
}

感谢您的帮助。

英文:

I have a list actions calling different methods. All actions will need to run in different threads in parallel using Parallel.ForEach. All actions will return a bunch of info as an object Result. All Results are added to a log using a lock on the log.

My question and possible issue is: can the action methods have a return value to be retrieved and used inside the Parallel.ForEach and how would that be done? Also, how could the Result be safely used without being overwritten every time a thread finishes execution while another result is waiting for the log to be updated?

My current simplified code looks something like this:

    private Log _log;

    private void DoParallelWork()
    {
        List&lt;Action&gt; actions = new List&lt;Action&gt; { Action1, Action2, Action3, ...etc };
        Result result = new Result();

        System.Threading.Tasks.Parallel.ForEach(actions , action =&gt;
        {
            **result** = action(); // How can I get the result back to the ForEach?

            lock (_log)
            {
                UpdateLog(result);
            }
        });
    }
   
    private Result Action1() // same structure for Action2, 3, ...etc
    {
        return [call to the db to execute a stored procedure returning a Result object];
    }

    private void UpdateLog(Result result)
    {
        // update _log based on result
    }

Thanks for helping out

答案1

得分: 3

通常,如果你想从类似这样的并行操作中返回值,最好使用 PLINQ。

var results = actions
    .AsParallel()
    .Select(action => action())
    .ToList();

当然,你可以根据需要调整 lambda 表达式以添加锁定和日志记录。如果将其更改为块,则只需return操作的结果。

英文:

Typically if you want to return values from parallel operations like this, it's better to use PLINQ.

var results = actions
    .AsParallel()
    .Select(action =&gt; action())
    .ToList();

Of course, you can adapt the lambda to add in your locking and logging as necessary. Just return the result of the action, if you change it to a block.

答案2

得分: 1

Action 用于 void 方法。如果使用 Action,编译器会假设这些方法没有返回类型,这就是你不能按照你的意图使用它的原因。

如果有返回类型,你必须使用 Func 委托类型。在你的情况下,它是 Func<Result>,因为你的方法返回一个 Result 并且没有任何输入参数。这将是你的代码:

private void DoParallelWork()
{
    List<Func<Result>> actions = new List<Func<Result>> { Action1, Action2, Action3, ...etc };

    System.Threading.Tasks.Parallel.ForEach(actions , action =>
    {
        Result result = action();
        lock (_log)
        {
            UpdateLog(result);
        }
    });
}

请注意,我将 result 变量的声明放在了并行循环内部,因为否则线程会共享这个变量。这可能会导致一个方法的结果被另一个方法的结果覆盖。为了线程安全,重要的是尽量共享尽可能少的变量。

英文:

Action is for void methods. If you use Action, the compiler assumes that the methods don't have any return type and that is why you can't use it the way you intended.

If you have a return type, you have to use the Func delegate types. In your case, it is Func&lt;Result&gt;, because your methods return a Result and don't have any input parameters. This will be your code:

private void DoParallelWork()
{
    List&lt;Func&lt;Result&gt;&gt; actions = new List&lt;Func&lt;Result&gt;&gt; { Action1, Action2, Action3, ...etc };

    System.Threading.Tasks.Parallel.ForEach(actions , action =&gt;
    {
        Result result = action();
        lock (_log)
        {
            UpdateLog(result);
        }
    });
}

Note that I put the declaration of the result variable inside the parallel foreach, because otherwise the threads would share this variable. This might have the consequence that the result of one of the methods is overwritten with the result from another method. For thread-safety, it is important that you share as few variables as possible.

huangapple
  • 本文由 发表于 2023年7月7日 05:32:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76632638.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定