2

So I have this method that has only synchronous operations:

public void Foo(){
    //do something synchronously
    if(x == 0)
        return;
    else if(x < 5):
        //do something
        return;
    // do something extra
}

Due to some interface changes, that method has to be changed to asynchronous, despite having no async operations. And in order to have the async keyword, there must be an await keyword within its body. Would an equivalent method of the originally synchronous method be:

public async Task FooAsync(){
    //do something synchronously
    if(x == 0)
        await Task.CompletedTask;
    else if(x < 5):
        //do something
        await Task.CompletedTask;
    // do something extra
}

I'm not sure about this since the CompletedTask property indicates that it is a successful operation, which isn't the case for all these conditionals.

EDIT: Resolved in comments. Just because a method is denoted with an "async" suffix it does not mean that it needs an async modifier. The function would be written as shown below to adhere to interface changes:

public Task FooAsync(){
    //do something synchronously
    if(x == 0)
        return Task.CompletedTask;
    else if(x < 5):
        //do something
        return Task.CompletedTask;
    // do something extra
    return Task.CompletedTask;
}
avhhh
  • 229
  • 1
  • 3
  • 11
  • 6
    Just because the interface requires it to return a Task *does not mean it has to be marked as async*. You can leave it not marked as async, have the return type be a Task, then `return Task.CompletedTask`. – mason Jan 06 '21 at 19:58
  • 3
    Just `return Task.CompletedTask` and that's it. Forget about `async` and `await` – Camilo Terevinto Jan 06 '21 at 19:58
  • The interface also indicates that it is an async method (the method name is now FooAsync), so it wouldn't make sense NOT to make it async? – avhhh Jan 06 '21 at 19:59
  • 3
    No....Just because the interface declares the method name with the -Async suffix doesn't mean it has to be marked as async. Marking it as async when it's not necessary would just introduce an unnecessary state machine. – mason Jan 06 '21 at 20:00
  • 2
    @avhhh The interface would only indicate that it returns a `Task`. The `async` keyword is only needed if you have to use `await` and you only need `await` if some of your code should run after an asynchronous call, which is not the case for your code. – juharr Jan 06 '21 at 20:00
  • Oh I see, I didn't know this. I thought having the `async` keyword is necessary to identify a method that is named as -Async for consistency issues. Thanks @mason and @juharr – avhhh Jan 06 '21 at 20:06
  • 1
    You may find this interesting: [Eliding Async and Await](https://blog.stephencleary.com/2016/12/eliding-async-await.html). Eliding the async/await will change the behavior of your method in case of an exception. – Theodor Zoulias Jan 06 '21 at 20:09
  • 1
    @avhhh naming a method as ~Async denotes the method as "behaving in an asynchronous way", which is subtly different to "having an async modifier". See [here](https://stackoverflow.com/questions/15951774/does-the-use-of-the-async-suffix-in-a-method-name-depend-on-whether-the-async) – Caius Jard Jan 06 '21 at 20:16
  • 1
    (In my opinion, though your method returns a Task, it does not behave in an asynchronous way, and is one of those rare cases where it shouldn't be named ~Async) – Caius Jard Jan 06 '21 at 20:21
  • @CaiusJard just for my understanding, how else can a method behave asynchronously without having an `await` in its body? I also agree with the fact that it shouldn't be named async, but since it's part of an interface, it has to be changed unfortunately. – avhhh Jan 06 '21 at 20:22
  • 1
    There's multiple asynchronous patterns besides TAP. See [Microsoft documentation](https://learn.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/). – mason Jan 06 '21 at 20:25
  • 1
    Thanks for all the help everyone! – avhhh Jan 06 '21 at 20:53

1 Answers1

2

As others have noted, a task-like return type is an asynchronous signature, whereas async is an asynchronous implementation detail. It's entirely possible to have a Task-returning method not be async.

However, there is one important caveat: exceptions. When an asynchronous (i.e., Task-like-returning) method fails, the expected semantics is to capture the exception and place it on the returned task.

So, you wouldn't want to just return CompletedTask; you'd also want to capture exceptions and use Task.FromException:

public Task FooAsync()
{
  try
  {
    //do something synchronously
    if(x == 0)
        return Task.CompletedTask;
    else if(x < 5):
        //do something
        return Task.CompletedTask;
    // do something extra
    return Task.CompletedTask;
  }
  catch (Exception ex)
  {
    return Task.FromException(ex);
  }
}

At this point, there's rather a bit of boilerplate. You may prefer to use async without await, as such:

#pragma warning disable 1998
public async Task FooAsync()
#pragma warning restore 1998
{
    //do something synchronously
    if(x == 0)
        return;
    else if(x < 5):
        //do something
        return;
    // do something extra
}

The problem with that approach is that you will get compiler warnings unless you include the ugly pragmas to disable them. If you find yourself needing to do this in several places, I would recommend using some helper methods (stolen from my AsyncEx library):

public static class TaskHelper
{
#pragma warning disable 1998
    public static async Task ExecuteAsTask(Action func)
#pragma warning restore 1998
    {
        func();
    }

#pragma warning disable 1998
    public static async Task<T> ExecuteAsTask<T>(Func<T> func)
#pragma warning restore 1998
    {
        return func();
    }
}

used as such:

public Task FooAsync() => TaskHelper.ExecuteAsTask(() =>
{
    //do something synchronously
    if(x == 0)
        return;
    else if(x < 5):
        //do something
        return;
    // do something extra
});

which allows you to wrap synchronous methods quickly and easily.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Sorry again ;), Is wrapping an exception with `Task.FromException` suggested only to satisfy async-await "contract" of thrown exceptions? – Basin Jan 07 '21 at 02:26
  • 1
    @Basin: It's more of a task-like-returning contract rather than an `async`/`await` contract. And yes, it's to provide the expected semantics. – Stephen Cleary Jan 07 '21 at 03:06