Using Async/Await For Multiple Tasks

Using async/await for multiple tasks


int[] ids = new[] { 1, 2, 3, 4, 5 };
Parallel.ForEach(ids, i => DoSomething(1, i, blogClient).Wait());

Although you run the operations in parallel with the above code, this code blocks each thread that each operation runs on. For example, if the network call takes 2 seconds, each thread hangs for 2 seconds w/o doing anything but waiting.

int[] ids = new[] { 1, 2, 3, 4, 5 };
Task.WaitAll(ids.Select(i => DoSomething(1, i, blogClient)).ToArray());

On the other hand, the above code with WaitAll also blocks the threads and your threads won't be free to process any other work till the operation ends.

Recommended Approach

I would prefer WhenAll which will perform your operations asynchronously in Parallel.

public async Task DoWork() {

int[] ids = new[] { 1, 2, 3, 4, 5 };
await Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

In fact, in the above case, you don't even need to await, you can just directly return from the method as you don't have any continuations:

public Task DoWork() 
{
int[] ids = new[] { 1, 2, 3, 4, 5 };
return Task.WhenAll(ids.Select(i => DoSomething(1, i, blogClient)));
}

To back this up, here is a detailed blog post going through all the
alternatives and their advantages/disadvantages: How and Where Concurrent Asynchronous I/O with ASP.NET Web API

C# multiple async tasks and where to properly await them when they use each other to complete?

I think you misinterpret the flow of data.

Here are your two methods written a bit more verbose

Method 1

var renderTask = RenderDocumentBuilder.Instance.GetRenderDocumentDirectiveAsync(previousPage, session);

var renderDocumentDirective = await renderTask;

var alexaResponse = new Response();

alexaResponse.shouldEndSession = null,
alexaResponse.directives = new List<IDirective>();
alexaResponse.directives.Add(renderDocumentDirective);

var buildTask = ResponseClient.Instance.BuildAlexaResponse(alexaResponse, session.alexaSessionDisplayType);

return await buildTask;

Method 2

var renderTask = RenderDocumentBuilder.Instance.GetRenderDocumentDirectiveAsync(previousPage, session);

var alexaResponse = new Response()
alexaResponse.shouldEndSession = null,
alexaResponse.directives = new List<IDirective>();
alexaResponse.directives.Add(await renderTask);

var buildTask = ResponseClient.Instance.BuildAlexaResponse(alexaResponse, session.alexaSessionDisplayType);

return await buildTask;

So you see that the only real difference is that methode 2 creates the Response object, sets shouldEndSession and creates the List object before or it awaits the renderTask.

Method 2 might be beneficial, but this depends on how GetRenderDocumentDirectiveAsync is implemented (i.e. truely async). But even if it is, it is highly unlikly that method 2 brings any performance gains as there is not much difference between both methods.

That said, I would go with method 1, because it looks more like sync code and in most cases you want to await a Task as soon you have it available, because await/async is mainly about freeing threads to do other stuff and not about parallalism.

One method in many Tasks async/await

You said:

I want [the second attempt] to wait for the first refresh API finish

You can save a reference to your Task and, if found, await it. If not found, then start the task:

actor Refresh {
var task: Task<Void, Never>?

func refresh(value: Int) async {
try! await Task.sleep(nanoseconds: 100_000_000) // imitation API CALL
print("Try to refresh: \(value)")
}

func mockCallAPI(value: Int) async {
if let task = self.task {
_ = await task.result
return
}

task = Task {
await refresh(value: value)
task = nil
}
}
}

Apple showed example of this pattern in the code provided the with WWDC 2021 video, Protect mutable state with Swift actors (but this code is not on the web site; only provided in the Developer app). See How to prevent actor reentrancy resulting in duplicative requests?

Their example is more complicated (a pattern to avoid duplicate network requests from being initiated by some image cache/downloader), but the kernel of the idea is the same: Save and await the Task.

How to use async/await in scenario with multiple tasks with each task requiring a new async/await

It might be more sensible to split it by customer and async within that:

private async Task<Card> FindCardForCustomerAsync(string customer)
{
var account = await _service.FindAccountAsync(customer);
return await _service.FindCardAsync(account);
}

public async Task<Data> FindCustomersWithCards(string[] customers)
{
var cardsTasks = customers.Select(FindCardForCustomerAsync);
var cards = await Tasks.WhenAll(cardsTasks)

}

However, it's worth considering the balance of efficiencies of how your FindAccounts and FindCards work. E.g. if they work as a single SELECT pushed to a database then the greater concurrency of turning it into multiple smaller bits of work may not be worth the greater amount of overhead that has. It can be the case that waiting for 20 or even 200 results is only marginally slower than waiting for 1, and then splitting into 20 requests gains very little, even before the extra connections involved are considered.

How to await multiple tasks, that were created inside await foreach?

The given example should work, especially if you expect fairly few items.

Since you have no (visible) mechanism to limit the number of concurrent tasks you might suffer problems if you have a lots of items. You might for example exhaust the threadpool. If the amount of work done per item is very small it might also benefit from processing batches of items. To handle cases like these you might consider using something like DataFlow, it should allow you to specify the degree of parallelism.

How to parallel process multiple async task foreach loop

Since you are not awaiting the call to GetIsConnected() in your AFTER example, the loop will continue and the value may or may not be checked against what it would be once the call to GetIsConnected() is finished, what I think you'd want to do is add another method with a return type of Task and let the method call GetIsConnected() and await it inside the method then call GetCarDetailsAsync() if necessary:

//changed to return type of Task, as async void should be avoided
//unless this is an event handler method
private async Task Refresh()
//private async void Refresh() //change to this if event handler
{
List<Task> listOfTasks = new List<Task>();
foreach (var item in Cars.ToList<Carnet>())
{
listOfTasks.Add(GetCarDetails(item));
}
await Task.WhenAll(listOfTasks).ConfigureAwait(false);
}

//method to await the call to GetIsConnect()
//and then call GetCarDetailsAsync() if necessary
private async Task GetCarDetails(Carnet c)
{
await c.GetIsConnected();
if (c.IsConnected)
{
await c.GetCarDetailsAsync();
}
}

Run four tasks with a preset schedule in C#

Would do it like this then.

    static void Main(string[] args)
{
var task1 = runTask1();
var task2 = runTask2();

Task.WhenAny(task1, task2).ContinueWith(task =>
{
var task3 = runTask3();

Task.WaitAll(task1, task2, task3);

var task4 = runTask4();
task4.Wait();
});
}

How to simplify multiple awaits with a single 'await Task.WhenAll'?

If you want both task to execute simultaneously, then don't await the methods. Instead pass their tasks into a variables and call them in Task.WhenAll

public async Task UpdateData() {
var month = (cbMonths.SelectedItem as MonthView).ID;
var year = (cbYears.SelectedItem as YearView).ID;
var deviceTypeID = (int)DeviceType;
var task1 = GetCalendar(month, year, deviceTypeID);
var task2 = GetWorkTypes();

await Task.WhenAll(task1, task2);

var calendar = task1.Result;
var workTypes = task2.Result;
}

Also note that you should avoid async void methods.

Running multiple async tasks and waiting for them all to complete

Both answers didn't mention the awaitable Task.WhenAll:

var task1 = DoWorkAsync();
var task2 = DoMoreWorkAsync();

await Task.WhenAll(task1, task2);

The main difference between Task.WaitAll and Task.WhenAll is that the former will block (similar to using Wait on a single task) while the latter will not and can be awaited, yielding control back to the caller until all tasks finish.

More so, exception handling differs:

Task.WaitAll:

At least one of the Task instances was canceled -or- an exception was thrown during the execution of at least one of the Task instances. If a task was canceled, the AggregateException contains an OperationCanceledException in its InnerExceptions collection.

Task.WhenAll:

If any of the supplied tasks completes in a faulted state, the returned task will also complete in a Faulted state, where its exceptions will contain the aggregation of the set of unwrapped exceptions from each of the supplied tasks.

If none of the supplied tasks faulted but at least one of them was canceled, the returned task will end in the Canceled state.

If none of the tasks faulted and none of the tasks were canceled, the resulting task will end in the RanToCompletion state.
If the supplied array/enumerable contains no tasks, the returned task will immediately transition to a RanToCompletion state before it's returned to the caller.




Related Topics



Leave a reply



Submit