CleanCoder
CleanCoder

Reputation: 2869

Understanding Async/await properly. How is it correct?

Imagine the following situation. There is an UI and s long running operation must be called without blocking the Main thread. the long running operation calls intself some other methods, which don't interact with the UI thread.

In Most cases, the methods which are called from Method B and C have synchronous alternatives to their Async counterparts. The question is: can they be used safely instead of their async counterpart? Lets say DbContext.DbSet.Add instead of AddAsync

// UI
public async Task UiMethodAsync()
{
    var result = await MethodAAsync();
}

// some component
public async Task<bool> MethodAAsync()
{
    return await MethodBAsync().ConfigureAwait(false);
}

public async Task<bool> MethodBAsync()
{
    return await MethodCAsync().ConfigureAwait(false);
}

public async Task<bool> MethodCAsync()
{
    return await DbContext.Set<TEntit>.AnyAsync().ConfigureAwait(false);
}

My question is: Is it neccessary to make all the methods asynchronous to prevent UI thread from blocking or would it be eben good enough to make Method B and C synchronously like this:

// UI
public async Task UiMethodAsync()
{
    var result = await MethodAAsync();
}

// some component
public Task<bool> MethodAAsync()
{
    return Task.FromResult(MethodB());
}

public bool MethodB()
{
    return MethodC();
}

public bool MethodC()
{
    return DbContext.Set<TEntit>.Any();
}

of course this depends on what MethodB and C are doing, if they interact with the ui thread or not. but let's say they don't and just have to calculate things and return a result.

Is there a need to make them asynchronous as well? I guess no. I think it probably avoids unnecessary overhead for managing tasks and threads.

Upvotes: 1

Views: 136

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 456322

Generally speaking, if you want to make something asynchronous, you start at the lowest level - the APIs that are actually doing I/O work. In your example, AnyAsync would be the first thing made asynchronous. Then allow the asynchrony to grow from there (up through MethodC, then MethodB, then MethodA, and finally UiMethod).

It is normal to end up with asynchrony going "all the way".

Is there a need to make them asynchronous as well? I guess no.

Yes. If you want them to be asynchronous, then they need to be asynchronous. Task.FromResult is for synchronous implementations; they won't be asynchronous.

Imagine the following situation. There is an UI... the methods which are called from Method B and C have synchronous alternatives to their Async counterparts. The question is: can they be used safely instead of their async counterpart?

One thing you can get away with in UI world is by wrapping calls to synchronous methods in Task.Run. E.g.:

public async Task UiMethodAsync()
{
  var result = await Task.Run(MethodA);
}

This is a useful technique for situations where you have a client-side app, don't want to block the UI, but also don't want to take the time to convert all the code to being asynchronous. Note that this is not appropriate for server-side applications such as ASP.NET.

Upvotes: 1

Buh Buh
Buh Buh

Reputation: 7545

Just adding the async keyword is not enough to make your code not block.
A function returning a Task will still block unless the calls are async all the way down.

It's difficult to explain, but to highlight one bit of your code:

public async Task<bool> MethodA()
{
    return Task.FromResult(MethodB());
}

Is equivalent to

public async Task<bool> MethodA()
{
    bool b = MethodB();
    return Task.FromResult(b);
}

It is clear now that the first line of code is blocking, and the Task isn't created until the second line.

Upvotes: 2

Theodor Zoulias
Theodor Zoulias

Reputation: 43379

A method that returns a Task creates the expectation that will not block the calling thread. At least not for a substantial amount of time. But this is not enforced by the async-await machinery. It is possible, and actually very easy, to write a method that breaks this expectation. For example:

public Task DoStuffTheWrongWayAsync()
{
    Thread.Sleep(1000); // Simulate a heavy computation, or a blocking call
    return Task.CompletedTask;
}

Any thread that calls this method will be blocked for one second, and then will be handed a completed task. I don't know if this antipattern has an established name. Fake-asynchrony comes in mind, although this can also be used for the case that the caller is not blocked, but another poor thread is blocked instead. The bottom line is that a well behaved asynchronous method should return a Task immediately, leaving the calling thread free to do other work (like responding to UI events if it's a UI thread).

Upvotes: 2

Related Questions