1

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.

CleanCoder
  • 2,406
  • 1
  • 10
  • 35
  • 1
    if MethodC has nothing to await, then it would have no reason to be marked as async. so-on and so-forth – TheGeneral Mar 24 '20 at 11:49
  • That pretty much depends on what methods B and C do. If they do some IO, it is a good idea to make them async as well. If they do just CPU-intensive work, you can take the second approach as well. – Nick Mar 24 '20 at 11:50
  • In Most cases, the methods which are called from Method B and C has a synchronous alternative to the used Async call. The questino is, if they can be used safelly instead of their async counterpart then. Lets say DbContext.DbSet.Add instead of AddAsync – CleanCoder Mar 24 '20 at 11:51
  • If MethodC is doing something that needs await, each calling method should be marked as async, and each call awaited – TheGeneral Mar 24 '20 at 11:53
  • @SvenKrauter clearly the async version of a method does the same job of the sync version. Async is a different technology of execution by the .NET. Please have a read at [docs](https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/) – Phate01 Mar 24 '20 at 12:01
  • @SvenKrauter: If you want be sure not to block the main thread, you could run the, either synchronous or asynchronous, method on a background thread. Whether an async method blocks the calling thread depends entirely on the implementation. It might look and have the signature of an async method but behave just like a synchronous (blocking) method. You can't know for sure unless you look at the implementation of it. Assing the `async` keyword to a method doesn't automagically makes it asynchronous. – mm8 Mar 24 '20 at 12:16
  • As a side note, according to the [guidelines](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap#naming-parameters-and-return-types) the asynchronous methods should have the suffix `Async`. So your `UiMethod` method should be renamed to `UiMethodAsync` etc. – Theodor Zoulias Mar 24 '20 at 12:28
  • Related: https://stackoverflow.com/questions/21033150/any-difference-between-await-task-run-return-and-return-task-run – Hans Kesting Mar 24 '20 at 12:37
  • @HansKesting [that](https://stackoverflow.com/questions/21033150/any-difference-between-await-task-run-return-and-return-task-run) question has no relation to this question. That question is about two alternatives that are practically equivalent regarding behavior and scalability (choosing one is a matter of taste). This question is about two alternatives that differ drastically in behavior and scalability (one is clearly preferable to the other). – Theodor Zoulias Mar 24 '20 at 13:00
  • 1
    @Theodor Zoulias. The method names were only chosen for demonstration purpose. anyways I updated the code to avoid bad practice to others. – CleanCoder Mar 24 '20 at 14:44

3 Answers3

2

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.

Buh Buh
  • 7,443
  • 1
  • 34
  • 61
  • It was clear before that the call to MethodB will block the executing thread. BUT what is not clear: Will the UI/Main thread be blocked if MethodAAsync is called with async await? UI/Main-Thread then is not the same as the thread which executes Method B and C – CleanCoder Mar 24 '20 at 14:41
  • Yes it will be blocked. A B & C will all be on the same UI thread. Using async/await keywords is not enough to create a new thread. – Buh Buh Mar 24 '20 at 14:44
  • Why it will be blocked? – CleanCoder Mar 24 '20 at 14:45
  • Why do you think it would be on a different thread? You only switch to a different thread via something like `Task.Run()` or `DbContext.Set.AnyAsync()` – Buh Buh Mar 24 '20 at 14:48
  • with that logic, why should AnyAsync put something on a different thread? Does it call Task.Run()? – CleanCoder Mar 24 '20 at 14:51
  • Yes or at least it must call something similar. – Buh Buh Mar 24 '20 at 14:53
  • Does that code compile? Shouldn't the async keyword be removed? – Paulo Morgado Mar 24 '20 at 23:12
  • @PauloMorgado I'm not trying to compile SO questions. That's a little off topic for me. – Buh Buh Mar 25 '20 at 10:29
  • @Paulo async without await compiles. but you get the warning the code will be executed synchronously. async only allowd to use the wait keyword. anyways I removed it from the sample code. – CleanCoder Mar 26 '20 at 11:01
  • @Buh Buh. If Async/await is not enough to run something on a different thread, then I totally don't get what advantage it has to use async/await. Its primarily use is to run something on a different thread to not block the main/UI thread. That sounds highly unlogical. – CleanCoder Mar 26 '20 at 11:01
  • Let's take this to chat: https://chat.stackoverflow.com/rooms/210386/https-stackoverflow-com-questions-60830415 – Buh Buh Mar 26 '20 at 14:23
2

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).

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
1

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    I chose Task.Run to solve the problem of not blocking the UI. One question still remains: if async/await does not run something on a different thread. what is the advantage in using it then? Only for parallelism to do something else in between, *with* blocking the current thread as well? – CleanCoder Mar 27 '20 at 02:34
  • `async` is a form of concurrency without using threads. The whole point is to free up threads. For client apps, freeing up the UI thread allows responsiveness. For server apps, freeing up worker threads allows scalability. – Stephen Cleary Mar 27 '20 at 02:45
  • why does asyn/await fee up any thread if it is not creating new threads? is it about parallelismn without extra threads? – CleanCoder Mar 27 '20 at 14:18