【问题标题】:Parallel Tasks being handled as sequential并行任务按顺序处理
【发布时间】:2011-03-04 15:10:09
【问题描述】:

我试图围绕整个并行编程概念,主要关注任务,所以我一直在尝试这种情况,例如,多达 9 个并行任务会在随机的一段时间内执行它们的工作:

/// <remarks>
/// A big thank you to the awesome community of StackOverflow for 
/// their advice and guidance with this sample project
/// http://stackoverflow.com/questions/5195486/
/// </remarks>

RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
byte[] buffer = new byte[4];
random.GetBytes(buffer);

// Creates a random number of tasks (not less than 2 -- up to 9)
int iteration = new Random(BitConverter.ToInt32(buffer, 0)).Next(2,9);
Console.WriteLine("Creating " + iteration + " Parallel Tasks . . .");
Console.Write(Environment.NewLine);

Dictionary<int, string> items = new Dictionary<int, string>();

for (int i = 1; i < iteration + 1; i++) // cosmetic +1 to avoid "Task N° 0"
{
    items.Add(i, "Task # " + i);
}

List<Task> tasks = new List<Task>();

// I guess we should use a Parallel.Foreach() here
foreach (var item in items)
{
    // Creates a random interval to pause the thread at (up to 9 secs)
    random.GetBytes(buffer);
    int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);

    var temp = item;
    var task = Task.Factory.StartNew(state =>
    {
        Console.WriteLine(String.Format(temp.Value + " will be completed in {0} miliseconds . . .", interval));
        Thread.Sleep(interval);

        return "The quick brown fox jumps over the lazy dog.";

    }, temp.Value).ContinueWith(t => Console.WriteLine(String.Format("{0} returned: {1}", t.AsyncState, t.Result)));

    tasks.Add(task);
}

Task.WaitAll(tasks.ToArray());

但不幸的是,它们是按顺序而不是并行处理的。

如果你们能在这里帮助我,我会非常高兴——也许我应该使用 Parallel.ForEach 而不是常规的?

再次感谢任何建议。

编辑 两次更新代码示例以反映评论者的贡献。

【问题讨论】:

    标签: c# parallel-processing task task-parallel-library


    【解决方案1】:

    您在循环中中的每个任务上调用task.Result...这意味着它将在创建下一个任务之前等待一个任务的结果。试试这个:

    // Create all the tasks
    foreach (var item in items)
    {
        // ... Create and start tasks as before
        tasks.Add(task);
    }
    
    // Now wait for them all, printing the results
    foreach (var task in tasks)
    {
        Console.WriteLine(task.AsyncState + " returned: " + task.Result);
    }
    

    现在这将阻止立即创建的 first 任务 - 所以即使(比如说)第 5 个任务更早完成,在第一个任务完成之前您不会看到任何结果...但它们将并行执行。

    接下来,您需要像这样更改循环:

    foreach (var item in items)
    {
        var itemCopy = item;
        // Use itemCopy in here instead of item
    }
    

    否则您将捕获循环变量,这被认为是有害的 (part 1;part 2)。

    但是是的,您可能应该改用Parallel.ForEach。值得理解为什么它之前失败了。

    【讨论】:

    • 感谢乔恩的评论!我现在实际上正在更新示例。虽然我在尝试访问循环范围之外的 task.Result 时仍然遇到问题——我是否应该以更易于访问的类型收集所有返回值?
    • @Nano:我已经给您提供了一个显示任务结果的示例 - 因为您正在存储对您在 tasks 列表中创建的每个任务的引用。这就是您在创建循环之外获得它们的方式。
    • 非常感谢。我想知道显示每个结果的方式是什么,但是一旦任何任务完成? -- 这当然是随机化每个线程的生命周期的目的。
    • @Nano:您可以考虑使用 Task.ContinueWith 为每个任务添加延续,或者使用 Task.WhenAny 等待 any 任务完成。
    【解决方案2】:

    调用Task.Result 阻止。由于您是在 foreach 内对项目执行此操作,因此您最终会创建一个任务,然后等待它完成,然后再移动到下一个项目。

    尝试将呼叫转移到 Task.Result 之外的 foreach

    List<Task<string>> tasks = new List<Task<string>>();
    
    
    foreach (var item in items)
    {
        random.GetBytes(buffer);
        int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);
    
        var task = Task.Factory.StartNew(state =>
        {
            Console.WriteLine(String.Format(item.Value + " will be completed in {0} miliseconds . . .", interval));
            Thread.Sleep(interval);
    
            return "The quick brown fox jumps over the lazy dog.";
    
        }, item.Value);
    
        tasks.Add(task);
    }
    
    foreach (var task in tasks)
    {
        Console.WriteLine(task.AsyncState + " returned: " + task.Result);
    } 
    

    编辑

    根据 cmets 的要求。这是一个使用ConinueWith 在每个任务完成时打印结果的版本。任务列表现在也可以是List&lt;Task&gt;。最后仍然需要WaitAll 调用,以确保在每个任务完成之前该方法不会返回,但每个任务都会在完成时打印它的结果。

    RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
    byte[] buffer = new byte[4];
    random.GetBytes(buffer);
    
    // Creates a random number of tasks (not less than 2 -- up to 9)
    int iteration = new Random(BitConverter.ToInt32(buffer, 0)).Next(2, 9);
    Console.WriteLine("Creating " + iteration + " Parallel Tasks . . .");
    
    Dictionary<int, string> items = new Dictionary<int, string>();
    
    for (int i = 1; i < iteration + 1; i++) // cosmetic +1 to avoid "Task N° 0"
    {
        items.Add(i, "Parallel Task N° " + i);
    }
    
    List<Task> tasks = new List<Task>();
    
    // I guess we should use a Parallel.Foreach() here
    foreach (var item in items)
    {
        // Creates a random interval to pause the thread at (up to 9 secs)
        random.GetBytes(buffer);
        int interval = new Random(BitConverter.ToInt32(buffer, 0)).Next(1000, 9000);
    
        // http://stackoverflow.com/questions/5195486/
        var temp = item;
        var task = Task.Factory.StartNew(state =>
        {
            Console.WriteLine(String.Format(temp.Value + " will be completed in {0} miliseconds . . .", interval));
            Thread.Sleep(interval);
    
            return "The quick brown fox jumps over the lazy dog.";
    
        }, temp.Value).ContinueWith(t => Console.WriteLine(t.AsyncState + " returned: " + t.Result));
        tasks.Add(task);
    }
    
    Task.WaitAll(tasks.ToArray());
    

    【讨论】:

    • 非常感谢迈克的评论!这对我来说很有意义,但问题是 task.Result 在这种 foreach 循环的范围之外是不可访问的。
    • @Nano - 它应该可以访问。您将每个任务存储在任务列表中,并且该列表是在循环之外创建的。它应该是List&lt;Task&lt;string&gt;&gt; 而不是List&lt;Task&gt;。那就是问题所在。 “任务”上没有 Result 属性,但 Task&lt;TResult&gt; 上有一个属性
    • 现在可以使用了,谢谢!尽管它同时显示所有结果,但仍然不是理想的输出,该示例更倾向于显示一种完成后立即报告的行为
    • @Nano - 很高兴它有帮助。您可以在任务上使用ContinueWith 方法来告诉它完成后要做什么。我将更新代码以显示它。
    • @Nano - 另请注意var temp = item 行是必需的,但仅适用于产生“Parallel Task N x will be completed in ...”行的Console.WriteLineContinueWith 中的代码将在不复制项目的情况下获得正确的值。删除它以查看“将完成”消息仅包含最后一个项目的值,但“quick brown fox”消息将是正确的。
    【解决方案3】:

    由于您在每个任务上调用 task.Result,因此您正在创建一个阻塞调用,导致循环等待该任务返回。

    您应该先创建所有任务,然后在单独的循环中读取结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2023-03-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多