2

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.

Zuiq Pazu
  • 285
  • 1
  • 4
  • 12

3 Answers3

1

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);
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Doesn't that start each task as soon as the previous task finishes though? Sorry if my question isn't so clear to reflect that, but I would like all tasks to start together. – Zuiq Pazu Sep 24 '15 at 13:07
  • @ZuiqPazu Oh. I see. I'll edit my answer to use `Task.WhenAny`, that's what you need. – Yuval Itzchakov Sep 24 '15 at 13:08
  • I do not think that that is correct either. As I mentioned in my original question I need to do something as soon as each task finishes off. It is my understanding that Task.WhenAny() only fires off for the first finishing task. – Zuiq Pazu Sep 24 '15 at 13:11
  • @ZuiqPazu It gives you the task as soon as you're finished, if you iterate through the entire list of tasks, you can get them all. See my answer. – Yuval Itzchakov Sep 24 '15 at 13:11
  • This has quadratic performance cost and I think it's rather dirty imperative code... – usr Sep 24 '15 at 13:12
  • @usr First of all, OP said nothing about performance being critical, although I'd love if you'd shed more light on how this has quadratic perfrormance cost. Second of all, I don't see how this code is "dirty", may be imperative, but dirty? – Yuval Itzchakov Sep 24 '15 at 13:14
  • Each WhenAny attaches N continuations. That's N*N such continuations. The Remove also by itself causes quadratic cost. I think it's dirty because it is imperative without a need for it. I guess that's very subjective. – usr Sep 24 '15 at 13:16
  • @usr To each his own I guess. – Yuval Itzchakov Sep 24 '15 at 13:21
  • @YuvalItzchakov - Your solution does work, and you correct in saying that I did not mention anything about performance, however like most other people, if there is a solution which takes considerably less performance - with minimal coding-differences, I would be prone to favour that answer. – Zuiq Pazu Sep 24 '15 at 13:22
  • @ZuiqPazu I agree. But, I also believe that you should really test your code and see if this is actually a bottleneck. I could say the same for using `Enumerable.Select` which allocates wrapped iterators, and for many things in the framework which are compiler generated and hidden from the programmers eye. It all comes down to where this code is running. If this is a hot-path, I agree. Otherwise, I'm not that sure that it will be noticeable at all. – Yuval Itzchakov Sep 24 '15 at 13:24
1

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.

razor118
  • 473
  • 4
  • 16
  • This I think would solve part of the problem. This way I don't have to call another method to handle displaying the results to UI. But this still would show the warning that I'm currently facing with my implementation. – Zuiq Pazu Sep 24 '15 at 14:03
1

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();
}
usr
  • 168,620
  • 35
  • 240
  • 369
  • Doesn't that only fire as soon as all tasks are finished? – Zuiq Pazu Sep 24 '15 at 13:15
  • @ZuiqPazu AwaitTask runs for each task as soon as it's finished. That'S what you want I think(?). – usr Sep 24 '15 at 13:16
  • Given the new edit, it now looks quite close to my original code. – Zuiq Pazu Sep 24 '15 at 13:17
  • @ZuiqPazu does this answer the question? Any remaining issues? – usr Sep 24 '15 at 13:17
  • This does answer the question. Slightly unrelated, but just to confirm before I accept your answer: would you say that async / await is the way to go for IO-reliant and CPU-lite functions - or would you suggest a different threading approach? – Zuiq Pazu Sep 24 '15 at 14:09
  • await + async IO is a great choice in client GUI apps and very rarely on the server. Here's my standard treatment of the subject: http://stackoverflow.com/a/25087273/122718 Why does the EF 6 tutorial use asychronous calls? http://stackoverflow.com/a/12796711/122718 Should we switch to use async I/O by default? Consider this question of yours evidence of the productivity cost associated with this stuff... – usr Sep 24 '15 at 14:10
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/90534/discussion-between-zuiq-pazu-and-usr). – Zuiq Pazu Sep 24 '15 at 14:13