【问题标题】:Task.WhenAll(IEnumerable): Tasks are started twice?Task.WhenAll(IEnumerable):任务启动两次?
【发布时间】:2018-02-03 06:09:34
【问题描述】:

我刚刚偶然发现Task.WhenAll 的重载之一,它以 IEnumerable 作为参数

public static Task WhenAll(IEnumerable<Task<TResult>> tasks)

我想我会用下面的小程序试试这个功能。

在测试类中:

// contains the task numbers that has been run
private HashSet<int> completedTasks = new HashSet<int>();

// async function. waits a while and marks that it has been run:
async Task<int> Calculate(int taskNr)
{
     string msg = completedTasks.Contains(taskNr) ?
         "This task has been run before" :
         "This is the first time this task runs";
    Console.WriteLine($"Start task {i} {msg}");

    await Task.Delay(TimeSpan.FromMilliseconds(100));

    Console.WriteLine($"Finished task {taskNr}");
    // mark that this task has been run:
    completedTasks.Add(taskNr);
    return i;
}

// async test function that uses Task.WhenAll(IEnumerable)
public async Task TestAsync()
{
    Console.Write("Create the task enumerators... ");
    IEnumerable<Task<int>> tasks = Enumerable.Range(1, 3)
        .Select(i => Calculate(i));
    Console.WriteLine("Done!");

    Console.WriteLine("Start Tasks and await");
    await Task.WhenAll(tasks);
    Console.WriteLine("Finished waiting. Results:");

    foreach (var task in tasks)
    {
        Console.WriteLine(task.Result);
    }
}

最后是主程序:

static void Main(string[] args)
{
     var testClass = new TestClass();
     Task t = Task.Run(() => testClass.TestAsync());
     t.Wait();
}

输出如下:

Create the task enumerators... Done!
Start Tasks and wait
Start task 1 This is the first time this task runs
Start task 2 This is the first time this task runs
Start task 3 This is the first time this task runs
Finished task 2
Finished task 3
Finished task 1
Finished waiting. Results:
Start task 1 This task has been run before
Finished task 1
1
Start task 2 This task has been run before
Finished task 2
2
Start task 3 This task has been run before
Finished task 3
3

显然每个任务都运行了两次!我做错了什么?

更奇怪的是:如果我在 Task.Whenall 之前使用 ToList() 枚举任务序列,则该函数按预期工作!

【问题讨论】:

  • “请注意,此函数不是异步的,也不是可等待的” - 它可等待的。函数不必标记为 async 即可等待 - 它只需要返回遵循等待等待模式的内容。 Task 是该模式的典型代表。
  • Sprry,那是我的第一个版本。后来我意识到这可能是问题所在,我将所有内容都更改为 async-await。更正了问题

标签: c# async-await


【解决方案1】:

您的问题是延迟执行。改变这一行

IEnumerable<Task<int>> tasks = Enumerable.Range(1, 3)
    .Select(i => Calculate(i));

var tasks = Enumerable.Range(1, 3)
    .Select(i => Calculate(i)).ToList();

Select() 不会立即执行“查询”,而是返回一个枚举数。仅当您使用此枚举器遍历任务时,才会为序列 1...3 调用内部 lambda。
在您的版本中,每次迭代 tasks 时,都会再次调用 Calculate(i) 并创建新任务。
使用.ToList(),枚举器执行一次,Task&lt;int&gt; 的结果序列存储在List&lt;Task&lt;int&gt;&gt; 中(并且在第二次枚举该列表时不会再次生成)。


当您调用Task.WhenAll(tasks) 时,此方法将遍历tasks,从而启动每个任务。当您稍后再次迭代时(使用 foreach 循环输出结果),查询会再次执行,从而启动新任务。

【讨论】:

  • 确实您的解决方案有效,正如我在问题末尾所写的那样。但是,使用延迟执行我希望它在 Task.Whenall 枚举序列时执行,显然 Task.WhenAll 枚举了两次。
  • Task.WhenAll 执行一次,然后在 for 循环中再次执行。
  • “然后你再做一次” - 好吧,枚举器再做一次...... :)
猜你喜欢
  • 1970-01-01
  • 2022-07-08
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
  • 2022-07-15
  • 2023-03-31
  • 2015-02-02
  • 2016-01-30
相关资源
最近更新 更多