Allaev Bekzod
Allaev Bekzod

Reputation: 159

Two ways of calling asynchronous method. C#

Are there differences between this two approaches? Or program runs similarly in this two cases? If there are differences could you say what are these differences.

First approach:

Task myTask = MyFunctionAsync();
await myTask;

Second approach:

await MyFunctionAsync();

Upvotes: 2

Views: 279

Answers (4)

Harald Coppoolse
Harald Coppoolse

Reputation: 30454

What really helped me to understand async-await was the cook analogy descrived by Eric Lippert in this interview. Search somewhere in the middle for async-await.

Here he describes a cook making breakfast. Once he put on the kettle to boil water for the tea, he doesn't wait idly for the water to cook. Instead he puts the bread in the toaster, tea in the teapot and starts slicing tomatoes: whenever he has to wait for another apparatus or other cook to finish his job, he doesn't wait idly, but starts the next task, until he needs the results for one of tasks previous tasks.

Async await, does the same: whenever another process has to do something, where your thread can do nothing but wait for the other process to finish, the thread can look around to see if it can do other things instead. You typically see async-await when another lengthy process is involved: Writing data to a file, Querying data from a database or from the internet. Whenever your thread has to do this, the thread can order the other process to do something while it continues to do other things:

Task<string> taskReadFile = ReadMyFileAsync(...);

// not awaiting, we can do other things:
DoSomethingElse();

// now we need the result of the file:
string fetchedData = await taskReadFile;

So what happens. ReadMyFileAsync is async, so you know that somewhere deep inside it, the method awaits. In fact, your compiler will warn you if you forget to await.

Once your thread sees the await, it knows that the results of the await are needed, so it can't continue. Instead it goes up the call stack to continue processing (in my example DoSomethingElse()), until it sees an await. It goes up the call stack again and continues processing, etc.

So in fact there is no real difference between your first and your second method. You can compare it with:

double x = Math.Sin(4.0)

versus

double a = 4.0;
double x = Math.Sin(a);

Officially the only difference is that after these statements you can still use a. Similarly you can use information from the task after the await:

Task<MyData> myTask = FetchMyDataAsync(...);
MyData result = await myTask;

// if desired you can investigate myTask
if (result == null)
{
    // why is it null, did someone cancel my task?
    if (Task.IsCanceled)
    {
        Yell("Hey, who did cancel my task!");
    }
}

But most of the times you are not interested in the task. If you don't have anything else to do while the task is executing, I'd just await for it:

MyData fetchedData = await FetchMyDataAsync(...)

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062502

Short version: "not really, at least not in an interesting way"

Long version: awaitables aren't limited to Task/Task<T>, so it is possible (trivially so, in fact) to create code that compiles fine with:

await MyFunctionAsync();

but doesn't compile with:

Task myTask = MyFunctionAsync();
await myTask;

simply because MyFunctionAsync() returns something that isn't a task. ValueTask<int> would be enough for this, but you can make exotic awaitables if you want. But: if we replace Task with var, i.e.

var myTask = MyFunctionAsync();
await myTask;

then now the only difference is that we can refer to myTask at other points in the code, if we want to. This isn't exactly uncommon; the two main scenarios being

  • combining multiple checks over concurrent code, perhaps using WhenAny or WhenAll
  • (usually in the case of ValueTask[<T>]) checking whether the awaitable completed synchronously, to avoid the state machine overhead in the synchronous case

Upvotes: 4

Marlonchosky
Marlonchosky

Reputation: 526

In that particular case, the 2 forms of code are executed in a similar way. Homewer, consider this:

public async Task<int> CalculateResult(InputData data) {
    // This queues up the work on the threadpool.
    var expensiveResultTask = Task.Run(() => DoExpensiveCalculation(data));

    // Note that at this point, you can do some other work concurrently,
    // as CalculateResult() is still executing!

    // Execution of CalculateResult is yielded here!
    var result = await expensiveResultTask;

    return result;
}

As the comments in the code above point out, between a task is running and the await call you can execute any other concurrent code.

For more information, read this article.

Upvotes: 3

JamesFaix
JamesFaix

Reputation: 8645

They are effectively the same. The difference is that the first way lets you do more steps before you wait for a response. So you could start many tasks concurrently in the first way, and then await them all together with await Task.WhenAll(myListOfTasks)

For example:

var myTasks = myEmployees.Select(e => ProcessPayrollAsync(e));
await Task.WhenAll(myTasks);

I would use the first way if you need to for concurrency and the second way if its a simple case because its shorter.

Upvotes: 3

Related Questions