Zuiq Pazu
Zuiq Pazu

Reputation: 295

Awaiting IEnumerable<Task<T>> individually C#

In short, I have a Task enumerable, and I would like to run each Task within the array in an await fashion. Each Task will perform a slow network operation and from my end I simply need to update the WinForm UI once the task is finished.

Below is the code I'm currently using, however I think this is more of a hack than an actual solution:

private void btnCheckCredentials_Click(object sender, EventArgs e)
{
    // GetNetCredentials() is irrelevant to the question...
    List<NetworkCredential> netCredentials = GetNetCredentials();

    // This call is not awaited. Displays warning!
    netCredentials.ForEach(nc => AwaitTask(ValidateCredentials(nc)));
}

public async Task<bool> ValidateCredentials(NetworkCredential netCredential)
{
    // Network-reliant, slow code here...
}

public async Task AwaitTask(Task<bool> task)
{
    await task;

    // Dumbed-down version of displaying the task results
    txtResults.Text += task.Result.ToString();
}

2nd line of btnCheckCredentials_Click() warning is shown:

Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

This actually works the way I wanted to, since I do not want to wait for the operation to complete. Instead I just want to fire away the tasks, and then do something as soon as each one of them finishes.

The Task.WhenAny() or Task.WhenAll() methods do function as I expect, since I would like to know of every task finishing - as soon as it finishes. Task.WaitAll() or Task.WaitAny() are blocking and therefore undesirable as well.

Edit: All tasks should start simultaneously. They may then finish in any order.

Upvotes: 2

Views: 120

Answers (3)

usr
usr

Reputation: 171206

Are you looking for Task.WhenAll?

await Task.WhenAll(netCredentials.Select(nc => AwaitTask(ValidateCredentials(nc)));

You can do all the completion processing you need in AwaitTask.

The await task; is a bit awkward. I'd do it like this:

public async Task AwaitTask(netCredential credential)
{
    var result = await ValidateCredentails(credential);

    // Dumbed-down version of displaying the task results
    txtResults.Text += result.ToString();
}

Upvotes: 1

razor118
razor118

Reputation: 473

You can fire and forget each task and add callback when task is completed.

private async void btnCheckCredentials_Click(object sender, EventArgs e)
{
List<NetworkCredential> netCredentials = GetNetCredentials();

    foreach (var credential in netCredentials)
    {
        ValidateCredentails(credential).ContinueWith(x=> ...) {

        };
     }
}

So instead of labda expression you can create callback method and know exactly when the particular task finished.

Upvotes: 1

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149588

You can do this by using Task.WhenAny marking it as async (async void here is fine since you're inside an event handler):

private async void btnCheckCredentials_Click(object sender, EventArgs e)
{
    // GetNetCredentials() is irrelevant to the question...
    List<NetworkCredential> netCredentials = GetNetCredentials();
    var credentialTasks = netCredentials
                          .Select(cred => ValidateCredentialsAsync(cred))
                          .ToList();

    while (credentialTasks.Count > 0)
    {
        var finishedTask = await Task.WhenAny(credentialTasks);

        // Do stuff with finished task.

        credentialTasks.Remove(finishedTask);
    }
}

Upvotes: 1

Related Questions