使用Task.WhenAll增加任务列表

时间:2021-06-17 02:16:01

Task.WhenAll(IEnumerable<Task>) waits for all tasks in the IEnumerable are complete --- but only the tasks in the list when it's first called. If any active task adds to the list, they aren't considered. This short example demonstrates:

Task.WhenAll(IEnumerable )等待IEnumerable中的所有任务都完成---但只有第一次调用时列表中的任务才会完成。如果任何活动任务添加到列表中,则不会考虑它们。这个简短的例子说明:

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

    public async Task  QuickExample()
    {
        for(int n =0; n < 6; ++n)
            _tasks.Add(Func1(n));

        await Task.WhenAll(_tasks);     
        Console.WriteLine("Some Tasks complete");

        await Task.WhenAll(_tasks);
        Console.WriteLine("All Tasks complete");
    }


    async Task Func1(int n)
    {
        Console.WriteLine($"Func1-{n} started");
        await Task.Delay(2000);
        if ((n % 3) == 1)
            _tasks.Add(Func2(n));
        Console.WriteLine($"Func1-{n} complete");
    }

    async Task Func2(int n)
    {
        Console.WriteLine($"Func2-{n} started");
        await Task.Delay(2000);
        Console.WriteLine($"Func2-{n} complete");
    }

This outputs:

这输出:

Func1-0 started
Func1-1 started
Func1-2 started
Func1-3 started
Func1-4 started
Func1-5 started
Func1-5 complete
Func1-3 complete
Func2-1 started
Func1-1 complete
Func1-0 complete
Func1-2 complete
Func2-4 started
Func1-4 complete
Some Tasks complete
Func2-4 complete
Func2-1 complete
All Tasks complete
Done

The second Task.WhenAll() solves the problem in this case, but that's a rather fragile solution. What's the best way to handle this in the general case?

第二个Task.WhenAll()在这种情况下解决了问题,但这是一个相当脆弱的解决方案。在一般情况下,处理此问题的最佳方法是什么?

4 个解决方案

#1


2  

You are modifying the List<> without locking it... You like to live a dangerous life :-) Save the Count of the _tasks before doing a WaitAll, then after the WaitAll check the Count of _tasks. If it is different, do another round (so you need a while around the WaitAll.

你正在修改List <>而没有锁定它......你喜欢过着危险的生活:-)在执行WaitAll之前保存_tasks的Count,然后在WaitAll之后检查_tasks的Count。如果不同,请再做一轮(所以你需要在WaitAll周围休息一下。

int count = _tasks.Count;

while (true)
{
    await Task.WhenAll(_tasks);

    lock (_tasks)
    {
        if (count == _tasks.Count)
        {
            Console.WriteLine("All Tasks complete");
            break;
        }

        count = _tasks.Count;
        Console.WriteLine("Some Tasks complete");
    }
}

async Task Func1(int n)
{
    Console.WriteLine($"Func1-{n} started");
    await Task.Delay(2000);

    if ((n % 3) == 1)
    {
        lock (_tasks)
        {
            _tasks.Add(Func2(n));
        }
    }

    Console.WriteLine($"Func1-{n} complete");
}

I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await the new Tasks from the Tasks that generated them, without cascading them to the _tasks collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Tasks to the _tasks collection.

我将添加第二个(可能更正确的解决方案),这与您正在执行的操作不同:您只需等待生成它们的任务中的新任务,而无需将它们级联到_tasks集合。如果A创建B,则A在B完成之前不会完成。显然,您不需要将新任务添加到_tasks集合中。

#2


1  

Asynchronous function will return to the caller on first await.
So for loop will be complete before you add extra tasks to original tasks list.

异步函数将在第一次等待时返回给调用者。因此,在将额外任务添加到原始任务列表之前,将完成循环。

Implementation of Task.WhenAll will iterate/copy tasks to local list, so added tasks after Task.WhenAll called will be ignored.

Task.WhenAll的实现将迭代/复制任务到本地列表,因此在调用Task.WhenAll之后添加的任务将被忽略。

In your particular case moving call to Func1 before await Task.Delay() could be a solution.

在您的特定情况下,在等待Task.Delay()之前将调用移动到Func1可能是一个解决方案。

async Task Func1(int n)
{
    Console.WriteLine($"Func1-{n} started");
    if ((n % 3) == 1)
        _tasks.Add(Func2(n));

    await Task.Delay(2000);
    Console.WriteLine($"Func1-{n} complete");
}

But if in real scenario calling of Func2 depend on result of some asynchronous method, then you need some other solution.

但是如果在实际情况下调用Func2取决于某些异步方法的结果,那么你需要一些其他的解决方案。

#3


0  

Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.

考虑一下;听起来工作正在从另一个线程提交到“任务列表”。从某种意义上说,“任务提交”线程本身也可能是你要等待的另一个任务。

If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll will yield a fully-completed payload.

如果您等待提交所有任务,那么您可以保证下次调用WhenAll将产生完全完成的有效负载。

Your waiting function could/should be a two-step process:

您的等待功能可能/应该是一个两步过程:

  1. Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
  2. 等待“任务提交”任务完成,表明所有任务都已提交
  3. Wait for all the submitted tasks to complete.
  4. 等待所有提交的任务完成。

Example:

例:

public async Task WaitForAllSubmittedTasks()
{
    // Work is being submitted in a background thread;
    // Wrap that thread in a Task, and wait for it to complete.
    var workScheduler = GetWorkScheduler();
    await workScheduler;

    // All tasks submitted!

    // Now we get the completed list of all submitted tasks.
    // It's important to note: the submitted tasks
    // have been chugging along all this time.
    // By the time we get here, there are probably a number of
    // completed tasks already.  It does not delay the speed
    // or execution of our work items if we grab the List
    // after some of the work has been completed.
    //
    // It's entirely possible that - by the time we call
    // this function and wait on it - almost all the 
    // tasks have already been completed!
    var submittedWork = GetAllSubmittedTasks();
    await Task.WhenAll(submittedWork);

    // Work complete!
}

#4


0  

Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while construct.

由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while结构。

while (_tasks.Any( t => !t.IsCompleted ) 
{
    await Task.WhenAll(_tasks);
}

This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.

这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。

#1


2  

You are modifying the List<> without locking it... You like to live a dangerous life :-) Save the Count of the _tasks before doing a WaitAll, then after the WaitAll check the Count of _tasks. If it is different, do another round (so you need a while around the WaitAll.

你正在修改List <>而没有锁定它......你喜欢过着危险的生活:-)在执行WaitAll之前保存_tasks的Count,然后在WaitAll之后检查_tasks的Count。如果不同,请再做一轮(所以你需要在WaitAll周围休息一下。

int count = _tasks.Count;

while (true)
{
    await Task.WhenAll(_tasks);

    lock (_tasks)
    {
        if (count == _tasks.Count)
        {
            Console.WriteLine("All Tasks complete");
            break;
        }

        count = _tasks.Count;
        Console.WriteLine("Some Tasks complete");
    }
}

async Task Func1(int n)
{
    Console.WriteLine($"Func1-{n} started");
    await Task.Delay(2000);

    if ((n % 3) == 1)
    {
        lock (_tasks)
        {
            _tasks.Add(Func2(n));
        }
    }

    Console.WriteLine($"Func1-{n} complete");
}

I'll add a second (probably more correct solution), that is different from what you are doing: you could simply await the new Tasks from the Tasks that generated them, without cascading them to the _tasks collection. If A creates B, then A doesn't finish until B finishes. Clearly you don't need to add the new Tasks to the _tasks collection.

我将添加第二个(可能更正确的解决方案),这与您正在执行的操作不同:您只需等待生成它们的任务中的新任务,而无需将它们级联到_tasks集合。如果A创建B,则A在B完成之前不会完成。显然,您不需要将新任务添加到_tasks集合中。

#2


1  

Asynchronous function will return to the caller on first await.
So for loop will be complete before you add extra tasks to original tasks list.

异步函数将在第一次等待时返回给调用者。因此,在将额外任务添加到原始任务列表之前,将完成循环。

Implementation of Task.WhenAll will iterate/copy tasks to local list, so added tasks after Task.WhenAll called will be ignored.

Task.WhenAll的实现将迭代/复制任务到本地列表,因此在调用Task.WhenAll之后添加的任务将被忽略。

In your particular case moving call to Func1 before await Task.Delay() could be a solution.

在您的特定情况下,在等待Task.Delay()之前将调用移动到Func1可能是一个解决方案。

async Task Func1(int n)
{
    Console.WriteLine($"Func1-{n} started");
    if ((n % 3) == 1)
        _tasks.Add(Func2(n));

    await Task.Delay(2000);
    Console.WriteLine($"Func1-{n} complete");
}

But if in real scenario calling of Func2 depend on result of some asynchronous method, then you need some other solution.

但是如果在实际情况下调用Func2取决于某些异步方法的结果,那么你需要一些其他的解决方案。

#3


0  

Consider this; it sounds like work is being submitted to the "Task List" from another thread. In a sense, the "task submission" thread itself could also be yet another Task for you to wait on.

考虑一下;听起来工作正在从另一个线程提交到“任务列表”。从某种意义上说,“任务提交”线程本身也可能是你要等待的另一个任务。

If you wait for all Tasks to be submitted, then you are guaranteed that your next call to WhenAll will yield a fully-completed payload.

如果您等待提交所有任务,那么您可以保证下次调用WhenAll将产生完全完成的有效负载。

Your waiting function could/should be a two-step process:

您的等待功能可能/应该是一个两步过程:

  1. Wait for the "Task Submitting" task to complete, signalling all Tasks are submitted
  2. 等待“任务提交”任务完成,表明所有任务都已提交
  3. Wait for all the submitted tasks to complete.
  4. 等待所有提交的任务完成。

Example:

例:

public async Task WaitForAllSubmittedTasks()
{
    // Work is being submitted in a background thread;
    // Wrap that thread in a Task, and wait for it to complete.
    var workScheduler = GetWorkScheduler();
    await workScheduler;

    // All tasks submitted!

    // Now we get the completed list of all submitted tasks.
    // It's important to note: the submitted tasks
    // have been chugging along all this time.
    // By the time we get here, there are probably a number of
    // completed tasks already.  It does not delay the speed
    // or execution of our work items if we grab the List
    // after some of the work has been completed.
    //
    // It's entirely possible that - by the time we call
    // this function and wait on it - almost all the 
    // tasks have already been completed!
    var submittedWork = GetAllSubmittedTasks();
    await Task.WhenAll(submittedWork);

    // Work complete!
}

#4


0  

Since it seems that additional tasks can be created during the course of executing the original list of tasks, you will need a simple while construct.

由于在执行原始任务列表的过程中似乎可以创建其他任务,因此您需要一个简单的while结构。

while (_tasks.Any( t => !t.IsCompleted ) 
{
    await Task.WhenAll(_tasks);
}

This will check the list for any uncompleted tasks and await them until it catches the list at a moment when there are no tasks left.

这将检查列表中是否有任何未完成的任务并等待它们,直到它在没有任务的情况下捕获列表。

相关文章