45

In C#, when you override a method, it is permitted to make the override async when the original method was not. This seems like poor form.

The problem that makes me wonder is this: I was brought in to assist with a load test problem. At around 500 concurrent users, the login process would break down into a redirect loop. IIS was logging exceptions with the message "An asynchronous module or handler completed while an asynchronous operation was still pending". Some searching led me to think that someone was abusing async void, but my quick searches through the source found nothing.

Sadly, I was searching for async\s*void (regex search) when I should have been looking for something more like async\s*[^T] (assuming Task wasn't fully qualified.. you get the point).

What I later found was async override void onActionExecuting in a base controller. Clearly that had to be the problem, and it was. Fixing that up (making it synchronous for the moment) resolved the problem.

But it left me with a question: Why can you mark an override as async when the calling code could never await it?

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
Peter T. LaComb Jr.
  • 2,935
  • 2
  • 29
  • 44
  • 1
    async void is a correct pattern for event handlers. Just don't ignore warnings. – Hans Passant Mar 05 '16 at 17:56
  • I don't disagree - but, in this case, the original event is not async. – Peter T. LaComb Jr. Mar 05 '16 at 17:57
  • I'm guessing this isn't really [a practical, answerable problem](http://stackoverflow.com/help/on-topic); you're more interested in the philosphical/technical reasons why this is allowed. – Zev Spitz Mar 05 '16 at 17:57
  • Does this belong on Meta then? Be happy to move it. – Peter T. LaComb Jr. Mar 05 '16 at 17:58
  • 1
    Not Meta, but [programmers.SE](http://programmers.stackexchange.com). – Zev Spitz Mar 05 '16 at 17:58
  • 1
    A real life situation when being able to override a non-async method with async one is important is Xamarin. You have your `Activity` class with [methods like `OnCreate`](https://developer.xamarin.com/guides/android/application_fundamentals/activity_lifecycle/) that are called by the runtime, and the only way to use `async` in your program is to override these with adding `async`. – GSerg Mar 05 '16 at 18:00
  • 3
    I'm voting to close this question as off-topic because - based on comments I've re-posted to Programmers.SE: http://programmers.stackexchange.com/questions/311870/why-does-c-allow-you-you-make-an-override-async – Peter T. LaComb Jr. Mar 05 '16 at 18:02
  • 12
    I do not think this question is offtopic. Understanding that `async` is not a part of a method signature is important programming knowledge. Even if it was offtopic, you should have not cross-posted it. – GSerg Mar 05 '16 at 18:03
  • 1
    It's interesting to dig into the root cause of the exception: http://stackoverflow.com/a/28806198/869621. Basically, it's a side-effect of `await` queuing a callback using the `SynchronizationContext`, and you'd have the exact same issue if the overriding method was using directly the `SynchronizationContext` without being marked `async`. – Kevin Gosse Mar 05 '16 at 18:05
  • @GSerg But it doesn't quite fit into the SO model of _I have a problem doing X. I tried Y, and Z happened. How can I fix this?_. But it is certainly a [conceptual questions about software development](http://programmers.stackexchange.com/help/on-topic). – Zev Spitz Mar 05 '16 at 18:05
  • 1
    Lots of valid points, but to the case-in-point: the framework has no idea that the override is async, and because it is void - the async operations are left running after the request completes, leading to errors. Just seems like async-await would be cleaner if it were part of the method signature. Because of the way it was added, I don't think it would have been terrible to make awaiting an async call optional. – Peter T. LaComb Jr. Mar 05 '16 at 18:06
  • @ZevSpitz: Contrary to popular belief, SO is _not_ a debugging service. – Lightness Races in Orbit Mar 05 '16 at 18:18
  • Just a last comment, as my previous one was very clumsy. My point is: `async/await` is merely syntactic sugar (huge sugar, but still). Anything an async/await method is doing, it could do the same using other language constructs. Because of that, it doesn't make sense to ban the `async` keyword when overriding a method, or in any other case. To prevent one corner case, due to internal mechanics of ASP.NET, you'd be banning all other legitimate cases. – Kevin Gosse Mar 05 '16 at 18:20
  • Relevant: http://stackoverflow.com/q/19918728/11683 (+ questions linked from there). – GSerg Mar 05 '16 at 18:34
  • 1
    @KooKiz - How exactly should the compiler decide to allow you to await a call when you have a base-class typed parameter and the actual implementation is derived (and because it was allowed, overriden as asycn) ? If the compiler knows only that you've accepted the base class, you cannot await the method. It's not a corner case in any way I see it. – Peter T. LaComb Jr. Mar 05 '16 at 18:35
  • 2
    @PeterLaCombJr. The point of `async` is allowing you to use `await` **inside** of your method. It doesn't mean that your async method itself has to be awaited. So not being able to await an async method isn't a concern – Kevin Gosse Mar 05 '16 at 18:38
  • [This Eric Lippert article](https://blogs.msdn.microsoft.com/ericlippert/2010/11/11/asynchrony-in-c-5-part-six-whither-async/) has some interesting points. – Matthew Watson Mar 05 '16 at 18:53
  • @KooKiz - it is a concern, and is related to the question. I view it similarly to a return value - you don't have to care about them (or even store them), but they are there. You can't change them when you override a method though. I know that the override wasn't the source of the problem, but what I don't know is why the override was allowed. It doesn't seem to add muich value - the contents of the method end up being synchronous, and can (in this case) NEVER really benefit from async-await. – Peter T. LaComb Jr. Mar 05 '16 at 18:53
  • 1
    `and can (in this case) NEVER really benefit from async-await` Of course it can. I can override your method, and use the `await` keywoard to download some content with the httpclient and then do something with it. That the caller method won't wait for me to finish is irrelevant, that's not the purpose of the `async` keyword. You keep thinking of the `async` keyword as a hint for the caller method, when its purpose is limited to the callee – Kevin Gosse Mar 05 '16 at 18:59
  • `async void` is not a natural construct, and it causes all kinds of problems. However, the C# team decided to allow `async void` mainly to enable async event handlers, and for consistency they allow `async void` methods everywhere. Note that some other `async` languages such as F# and ES7 do not have the equivalent of an `async void`. – Stephen Cleary Mar 05 '16 at 20:26
  • It's no different to overriding a method that returns `1` with one that returns `2`. – Jon Hanna Mar 06 '16 at 04:13

3 Answers3

39

When the base class (or interface) declares a virtual method that returns a Task, you can override it as long as you return Task. The async keyword is just a hint to the compiler to transform your method into a state machine. Although the compiler does it's black magic on your method, the compiled method still returns a Task.


As for void virtual methods, you can override one without the async keyword (obviously) and start a non-awaited Task within it. That's kind of what happens when you override it with the async keyword and use await in the body. The caller would not await the created Task (since the "original" signature is void). Both cases are similar*:

public override void MyVirtualMethod()
{
    // Will create a non awaited Task (explicitly)
    Task.Factory.StartNew(()=> SomeTaskMethod());  
}

public override async void MyVirtualMethod()
{
    // Will create a non awaited Task (the caller cannot await void)
    await SomeTaskMethod();  
}

Stephen Cleary's article has some notes regarding this:

  • Void-returning async methods have a specific purpose: to make asynchronous event handlers possible.
  • You should prefer async Task to async void.

*The implementation of SomeTaskMethod, the underlying framework, the SynchronizationContext and other factors might and will cause different outcomes for each of the above methods.

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
shay__
  • 3,815
  • 17
  • 34
13

You can override async method because async is not a part of method signature. Actually async allows using await keyword in your method by creating a state machine inside.
You can find more info about async here: http://blog.sublogic.com/2012/05/14/async-isnt-really-part-of-your-method-signature/

Kirill Kobelev
  • 10,252
  • 6
  • 30
  • 51
Kirill
  • 7,580
  • 6
  • 44
  • 95
10

async isn't part of the "contract". It's an implementation detail that, in my opinion, unfortunately, appears in the wrong place.

It is perfectly legal to change a (non-async) method returning a Task into an async one (or vice versa) without that being a breaking change, and without requiring callers to recompile.

Further indication that it's not part of the contract is that you're not allowed to mark functions as async within interfaces and, as here, it's perfectly possible to override async with non-async or vice versa.

Damien_The_Unbeliever
  • 234,701
  • 27
  • 340
  • 448