【问题标题】:Run same code multiple times in parallel with different parameter使用不同的参数并行运行相同的代码多次
【发布时间】:2020-04-22 02:36:07
【问题描述】:

这个非常简单的例子:

        int numLanes = 8;
        var tasks = new List<Task>();
        for (var i = 0; i < numLanes; ++i)
        {
            var t = new Task(() =>
            {
                Console.WriteLine($"Lane {i}");
            });
            tasks.Add(t);
        }
        tasks.ForEach((t) => t.Start());
        Task.WaitAll(tasks.ToArray());

生产:

Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8
Lane 8

这与预期不同,参数i 未正确传递。我曾想过使用Action&lt;int&gt; 来包装代码,但不知道该怎么做。我不想写像 Task CreateTask(int i) 这样的专用方法,我对如何使用 lambdas 来做这件事很感兴趣。

执行此操作的正常方法是什么 - 以不同的参数值并行启动相同的代码多次?

【问题讨论】:

  • 是的,我知道,await 等不是WaitAll,它只是一个点头测试应用程序。
  • 出于兴趣可以我将此代码封装为带有参数的Action/local lambda,然后在循环中使用不同的参数调用它? Action&lt;T&gt;&lt;Action&gt; 似乎不相容......如果有人能就那个方向提供答案,那会很有趣

标签: c# task


【解决方案1】:

你有一个捕获的循环变量i,尝试在循环中添加临时变量并将它传递给Task

for (var i = 0; i < numLanes; ++i)
{
    var temp = i;
    var t = new Task(() =>
    {
        Console.WriteLine($"Lane {temp}");
    });
    tasks.Add(t);
}

进一步阅读How to capture a variable in C# and not to shoot yourself in the footforeach 循环在 C# 5 之前具有相同的行为,但根据上面的链接

随着 C# 5.0 标准的发布,这种行为被改变了 在每次循环迭代中声明迭代器变量,而不是 在编译阶段之前,但对于所有其他结构 类似的行为仍然没有任何变化

所以,你可以使用没有临时变量的foreach

【讨论】:

  • 我试图看看我是否可以强制我的 lambda 以 take 一个参数而不是捕获i,但不知道该怎么做所以?但这显然是我的代码问题的简单答案 - 谢谢
  • @Mr.Boy 我认为,捕获变量是标准方式,lambda 是如何工作的。请参阅我上面提到的文章。在引擎盖下编译器生成一个类并且拥有一个临时变量似乎是避免捕获的唯一方法
【解决方案2】:

您需要在for 循环内捕获值,否则所有Tasks 仍然指的是同一个对象:

for (var i = 0; i < numLanes; ++i)
{
    var innerI = I; // Copy the variable here
    var t = new Task(() =>
    {
        Console.WriteLine($"Lane {innerI}");
    });
    tasks.Add(t);
}

请参阅here 了解更多信息。

【讨论】:

    【解决方案3】:

    您可以使用 LINQ 为您传递给 Task 构造函数的每个 lambda 创建一个闭包:

    var tasks = Enumerable.Range(0, numLanes - 1)
        .Select(i => new Task(() => Console.WriteLine($"Lane {i}")));
    

    【讨论】:

    • 我后来发现,这里的一个警告是惰性评估意味着任务不一定在你认为的时候运行 (stackoverflow.com/questions/61349364/…)
    • 一旦您熟悉了 LINQ,延迟执行就成了第二天性。如果您需要立即执行,只需添加一个ToList(),它将立即迭代。
    【解决方案4】:

    另一种方法(在for 循环内不引入额外变量)是使用构造函数Task(Action&lt;object&gt;, object)

    int numLanes = 8;
    var tasks = new List<Task>();
    
    for (int i = 0; i < numLanes; ++i)
    {
        // Variable "i" is passed as an argument into Task constructor.
        var t = new Task(arg =>
        {
            Console.WriteLine("Lane {0}", arg);
        }, i);
        tasks.Add(t);
    }
    
    tasks.ForEach((t) => t.Start());
    Task.WaitAll(tasks.ToArray());
    

    C# 5 和以后的foreach 循环中,每次迭代都会引入一个新变量。因此在C# 5 及以后的版本中,可以使用foreach 创建任务,其中每个任务都捕获自己的循环变量(也无需在循环内引入额外的变量):

    int numLanes = 8;
    var tasks = new List<Task>();
    
    foreach (int i in Enumerable.Range(0, numLanes))
    {
        // A new "i" variable is introduced on each iteration.
        // Therefore each task captures its own variable.
        var t = new Task(() =>
        {
            Console.WriteLine("Lane {0}", i);
        });
        tasks.Add(t);
    }
    
    tasks.ForEach((t) => t.Start());
    Task.WaitAll(tasks.ToArray());
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-03-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-04-09
      • 1970-01-01
      相关资源
      最近更新 更多