Roelant M
Roelant M

Reputation: 1706

await Task.WhenAll vs ..select(async .. => await)

just a quick question. We're having some misunderstanding here.

we have:

var tasks = files.Select(async fileName => await IngestFileAsync(container, fileName));
var results = await Task.WhenAll(tasks);

I say that the first line still goes concurrent, but my fellow colleague says otherwise. Besides, he says that the second await has no meaning, because all actions are already performed.

is this code then the same:

var tasks = files.Select(fileName => IngestFileAsync(container, fileName));
var results = await Task.WhenAll(tasks);

as:

var tasks = files.Select(async fileName => await IngestFileAsync(container, fileName));
var results = Task.WhenAll(tasks);

Could someone shine some extra light on this?

cheers.

added: oke, so it will run concurrent.

However, can someone add some extra info what the difference is between these code snippets: https://dotnetfiddle.net/lzv2B7 https://dotnetfiddle.net/dMusus

(notice line 16, async and await). Is there any difference between those 2? What I would expect is that with async and await it will start directly, and without, that it will start when it comes to Await Task.WhenAll(tasks);

added for clearity - this is my code-:

   private async Task<Result> IngestFilesAsync(ICloudBlobContainer container, IEnumerable<string> files)
    {
        _logger.LogDebug("Start IngestFilesAsync");

        var tasks = files.Select(fileName => IngestFileAsync(container, fileName));
        var results = await Task.WhenAll(tasks);

        _logger.LogDebug("All tasks completed");

        if (results.Any(t => t.IsFailure))
        {
            return Result.Fail(string.Join(",", results.Select(f => f.Error)));
        }

        return Result.Ok();
    }

    private async Task<Result> IngestFileAsync(ICloudBlobContainer container, string fileName)
    {
        _logger.LogDebug("Start IngestFileAsync");
        var blob = container.GetBlockBlobReference(fileName);

        _logger.LogDebug("Blob retrieved");

        if (await blob.ExistsAsync())
        {
            using (var memoryStream = new MemoryStream())
            {
                _logger.LogDebug("Start download to stream");
                await blob.DownloadToStreamAsync(memoryStream);
                _logger.LogDebug("To mem downloaded");

                _logger.LogDebug("Start ftp-upload");

                return await _targetFTP.UploadAsync(memoryStream, fileName);
            }
        }

        _logger.LogWarning("Blob does not exists");

        return Result.Fail($"Blob '{fileName}' does not exist");
    }

where _targetFTP.UploadAsync(memoryStream, fileName); is again a task etc. etc.

Upvotes: 8

Views: 2334

Answers (1)

user743382
user743382

Reputation:

async x => await f() creates an anonymous function which returns a task. That task effectively wraps the one created by f: it will complete directly afterwards. In particular, this anonymous function returns an in-progress task as soon as possible.

.Select does not behave differently based on whether the enumeration type is one of a task. It allows the next result to be fetched directly, when the first returned task is still in progress.

The code fragments are not 100% identical, but the difference you're asking about doesn't exist.

The differences are small; the most visible change is probably in exception handling. Suppose you have two not-yet-implemented functions:

Task Sync() => throw new NotImplementedException();
async Task Async() => throw new NotImplementedException();

Here, var task = Sync(); obviously fails immediately. But var task = Async(); is different: that succeeds. The async keyword here forces a task to be created, which captures the thrown exception.

This same distinction also applies to .Select(x => Sync()) vs .Select(async x => await Sync()).

Upvotes: 10

Related Questions