PolarBear
PolarBear

Reputation: 342

Process a list of tasks asynchronously using Task.WhenAny

I create a list of tasks. As soon as some task in the list completes, I want to process its result (in this case, print the name of a blob).

The code (contained within an async function) reads as follows:

List<Task<BlobResultSegment>> taskList = new List<Task<BlobResultSegment>>();
BlobContinuationToken token = null;
do
{
    taskList.Add(blobContainer.ListBlobsSegmentedAsync(token));
}
while (token != null);
            
while(taskList.Any())
{
    Task<BlobResultSegment> completedTask = await Task.WhenAny(taskList);
    await completedTask.ContinueWith(task => {
        foreach (IListBlobItem item in task.Result.Results)
        {
            CloudBlockBlob blob = item as CloudBlockBlob;
            Console.WriteLine(blob.Name); // this statement causes the error
        }
    });
    taskList.Remove(completedTask);
}

The error that is produced: 'Object reference not set to an instance of an object.'

Why is the error produced and how can I fix it?

The task specified in the ContinueWith method should be executed after the preceding task has finished, so I do not understand what the problem is.

As a side note, the code runs if the statement containing the WriteLine method is removed.

Upvotes: 0

Views: 166

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 456477

You shouldn't use ContinueWith; it's a dangerous, low-level method.

Also, I strongly recommend against the "WhenAny then remove it from the list" approach. It's almost an antipattern. The code is much simpler if you introduce a helper asynchronous method instead.

On to the Azure-specific problems. First, the way ListBlobsSegmentedAsync works is that you need to call it one at a time, updating the token with the result of each call, like this:

List<IListBlobItem> blobs = new();
BlobContinuationToken token = null;
do
{
  var result = await blobContainer.ListBlobsSegmentedAsync(token);
  blobs.AddRange(result.Results);
  token = result.ContinuationToken;
} while (token != null);

foreach (var item in blobs)
{
  CloudBlockBlob blob = item as CloudBlockBlob;
  Console.WriteLine(blob.Name);
}

As for the null reference exception, it's most likely because there's a blob that isn't a CloudBlockBlob, e.g., CloudBlobDirectory. I'd recommend something like this:

foreach (var item in blobs)
{
  if (item is CloudBlob blob)
    Console.WriteLine(blob.Name);
  else if (item is CloudBlobDirectory dir)
    Console.WriteLine("Dir: " + dir.Prefix);
}

Upvotes: 4

Related Questions