user3310334
user3310334

Reputation:

Map results tasks in a list, Tasks.ForEach

The best I've been able to find in my research for a "do something with the results of tasks in a list as they complete" is this Microsoft page Process asynchronous tasks as they complete, unfortunately the example within accumulates the results of the tasks which I don't need to do; I only want to: spawn list of tasks, when a task completes, do something with its result.

So here I have adapted their example to demonstrate what I mean:

static async Task SumPageSizesAsync()
{
    IEnumerable<Task<int>> downloadTasksQuery =
        from url in s_urlList
        select ProcessUrlAsync(url, s_client);

    List<Task<int>> downloadTasks = downloadTasksQuery.ToList();
    while (downloadTasks.Any())
    {
        Task<int> finishedTask = await Task.WhenAny(downloadTasks);
        downloadTasks.Remove(finishedTask);
        Console.WriteLine($"Just downloaded ${finishedTask} bytes");
    }
}

After any download task finishes, print an alert that it has finished (for example).

How can I more succinctly accomplish the same thing?

Is

downloadTasksQuery.Select(async t => Console.WriteLine($"Just downloaded ${await t} bytes"));

a good solution?

Upvotes: 1

Views: 929

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 457217

research for a "do something with the results of tasks in a list as they complete" is this Microsoft page Process asynchronous tasks as they complete,

As I describe in my book (section 2.6 "Processing Tasks as They Complete"), this is usually the wrong thing to search for.

Instead of thinking about the problem that way, compose the problem at a different level. Don't think about it as:

  • Take a list of inputs
  • Do asynchronous operation A on each input concurrently
  • As each A completes, do operation B on the result of A

Instead, compose A and B together, and then you have:

  • Take a list of inputs.
  • Do asynchronous operation A+B on each input concurrently

The resulting code is much cleaner:

static async Task SumPageSizesAsync()
{
  // Composition is done with async+await
  var tasks = s_urlList.Select(async url =>
  {
    var result = await ProcessUrlAsync(url, s_client);
    Console.WriteLine($"Just downloaded {result} bytes");
  }).ToList();

  // Asynchronous concurrency is done with Select + Task.WhenAll
  await Task.WhenAll(tasks);
}

Upvotes: 1

Theodor Zoulias
Theodor Zoulias

Reputation: 43886

Yes, your approach is OK. Let's rewrite it in a less succinct way for clarity:

Task[] continuations = downloadTasksQuery
    .Select(async task =>
    {
        var result = await task;
        Console.WriteLine($"Just downloaded ${result} bytes")
    })
    .ToArray();

await Task.WhenAll(continuations);

The continuations will run on the current SynchronizationContext, if one exists, or on an unknown context otherwise. Without knowing the context, we can't say for sure if the continuations will be serialized or not.

Upvotes: 1

Related Questions