paski
paski

Reputation: 53

C# TaskWhenAll on method which is returing result depending on awaited call

i'm trying to make this code works in async way, but i got some doubts.

public async Task<string> GetAsync(string inputA, string inputB)
{
    var result = await AnotherGetAsync(inputA, inputB)
        .ConfigureAwait(false);

    return result.Enabled
        ? inputB
        : string.Empty;
}

I got some string input collection, and i would like to run this method on them. Then i would do Task.WhenAll, and filter for non empty strings.

But it won't be async as inside the method i'm already awaiting for the result right?

Upvotes: 5

Views: 119

Answers (3)

Theodor Zoulias
Theodor Zoulias

Reputation: 43812

No, it will be async. When you await a task inside an async method, the task will be awaited when the async method is invoked. Here is your example simplified, using explicit variables for the created tasks:

public async Task<string> GetAsync()
{
    var innerTask = InnerGetAsync();
    var result = await innerTask;
    var stringResult = result.ToString();
    return stringResult;
}

Later you create two tasks by invoking the GetAsync() method:

var task1 = GetAsync();
var task2 = GetAsync();

At this point the two tasks are running concurrently. Each one has invoked internally the InnerGetAsync() method, which means that the two innerTasks are also up and running, and awaited. Any code that follows the await will not run before the innerTask is completed.

The completion of each outer task (task1 and task2) is dependent on the completion of its innerTask. Any code that will await task1, will also implicitly await innerTask as well.

After creating the two tasks, you combine them with Task.WhenAll, then do something else, then await the combined task, and finally process the results:

Task<string[]> whenAllTask = Task.WhenAll(task1, task2);
DoSomethingElse();
string[] results = await whenAllTask;
ProcessResults(results);

It is important that the DoSomethingElse() method will run before the two tasks are completed. So at this point three things will be happening concurrently, the DoSomethingElse() and the two active tasks. The await inside the GetAsync() method is local to this method, and does not mean that the generated outer task will be automatically awaited upon creation. To process the results of task1 and task2 you must first await them as well.

Upvotes: 2

Silvermind
Silvermind

Reputation: 5944

I assumed the real question is:

If a single item is awaited inside method A, will this run sequential if I use Task.WhenAll for a range of items calling method A?

They will be run simultaneous with Task.WhenAll. Perhaps it is best explained with an example:

void Main()
{
    Test().GetAwaiter().GetResult();
}

private async Task<bool> CheckEmpty(string input)
{
    await Task.Delay(200);
    return String.IsNullOrEmpty(input);
}

private async Task Test()
{
    var list = new List<string>
    {
        "1",
        null,
        "2",
        ""
    };

    var stopwatch = new Stopwatch();
    stopwatch.Start();

    // This takes aprox 4 * 200ms == 800ms to complete.
    foreach (var itm in list)
    {
        Console.WriteLine(await CheckEmpty(itm));
    }

    Console.WriteLine(stopwatch.Elapsed);
    Console.WriteLine();
    stopwatch.Reset();
    stopwatch.Start();

    // This takes aprox 200ms to complete.
    var tasks = list.Select(itm => CheckEmpty(itm));
    var results = await Task.WhenAll(tasks); // Runs all at once.
    Console.WriteLine(String.Join(Environment.NewLine, results));
    Console.WriteLine(stopwatch.Elapsed);
}

My test results:

False
True
False
True
00:00:00.8006182

False
True
False
True
00:00:00.2017568

Upvotes: 2

Johnathan Barclay
Johnathan Barclay

Reputation: 20373

If we break this down:

AnotherGetAsync(inputA, inputB)

Returns a Task. Using the await keyword implicitly returns another Task to the code calling GetAsync(string inputA, string inputB).

Using the await keyword will free up the current thread, which is what makes the code asynchronous.

once the Task returned by AnotherGetAsync(inputA, inputB) is complete, result will be set to that Task.Result.

The remainder of the method will then resume:

return result.Enabled
    ? inputB
    : string.Empty;

Setting the Task.Result of the Task returned from GetAsync.

If GetAsync is to be run multiple times, you can use Task.WhenAll to wait for each resulting Task to complete:

Task.WhenAll(yourStringCollection.Select(s => GetAsync(s, "another string"));

Task.WhenAll itself returns another Task that will complete when all the GetAsync tasks have completed:

var strings = await Task.WhenAll(yourStringCollection.Select(s => GetAsync(s, "another string"));
var nonEmptyStrings = strings.Where(s => s != string.Empty);

Upvotes: 2

Related Questions