19

What is the correct way to mark async method as not implemented/not supported or invalid operation. For the simplicity I would use only NotImplementedException in examples, but the question applies to the NotSupportedException and InvalidOperationException as well.

In a sync way one would simple throw the exception:

public override void X() {
    throw new NotImplementedException();
}

What would be the equivalent of this code in async world?

/* 1 */ public override Task XAsync() {
    throw new NotImplementedException();
}

Or

/* 2 */ public override Task XAsync() {
    return Task.FromException(new NotImplementedException());
}

What are the complications of these approaches? Is there any better method?


To avoid the "Nah, you don't need a method to be async here"/"It's not async" I would say that the method implements some interface or abstract class.


Some methods which I'm not considering:

/* 3 */ public async override Task XAsync() { // here is an CS1998 warning 
    throw new NotImplementedException();
}

The compiler would just generate the useless state machine, which is semantically equivalent to 2

/* 4 */ public async override Task XAsync() {
    await Task.Yield();
    throw new NotImplementedException();
}

This is the same as 3, but with added await on Task.Yeild();

hazzik
  • 13,019
  • 9
  • 47
  • 86
  • 3
    Good question. The first option throws the exception when the method is invoked, the second when the result is awaited. I have a small preference for the first option, since it "fails early", but I'm curious at what the experts will have to say. – Heinzi May 04 '17 at 20:57
  • Maybe it helps: http://stackoverflow.com/a/13254787/316799 – Felipe Oriani May 04 '17 at 20:58
  • yeah, @FelipeOriani saw that, thanks – hazzik May 04 '17 at 21:00
  • "Unless you clarify what exact scenario you trying to cover" we are trying to make an async version of NHibernate:) https://github.com/nhibernate/nhibernate-core/pull/588 – hazzik May 04 '17 at 21:05
  • Neither of your options is an `async` method; that's another option to consider. –  May 04 '17 at 21:14
  • 1
    @hazzik Yes. Did you not understand my comment? I'm saying `async Task XAsync() { throw ...; }` is another option. With the `async` keyword. –  May 04 '17 at 21:21
  • @AlexeiLevenkov Your method there is not "truly async". In fact, it's synchronous. It'd behave identically to the first option shown. Calling `Task.Yield` doesn't do anything if you just ignore the returned `Task`. – Servy May 04 '17 at 21:31
  • @hvd Ah, sorry. But [it's just an overcomplicated](https://tryroslyn.azurewebsites.net/#b:master/f:r/K4Zwlgdg5gBAygTxAFwKYFsDcBYAUKSWRFDAOgBUALAJ1QEMATQiukAaxB1zwAdgAjADZgAxjBGDWIGAGEYAbzwxlMPkNExyrNjAAaARgCCIBBBEAKAJQKlKu8hoB7AO4wIqVwDlHyAJLoeQQxUCDQGAFEADxFUHmQwRwgrLjsAX1tlNWExVlMxLXY9ACZjPKsbXDt7J1d3Lx9/QODQ1Ajo2PjE5IyYVOUerI0CnV0AZlKzcsVcAEgZ2mRgaghNbVIAMWpHdCiYuISkuphvPwCg9BCw3Y6Dq0sUlXTKlQGBbJhcs1XC3QAWCYs1mmVRUdGcdDAyG+bFIAE0wKhBAxus8QQ4trUPMcGmdmld2vsuvcek9UkA=) version of the second method with the generated state machine overhead – hazzik May 04 '17 at 21:37
  • @Servy you are absolutely correct - without `await`ing `Task.Delay()` my suggestion was totally pointless (hazzik's version in the question actually demonstrates the case when there is very significant difference with finishing code on thread-pool thread and potentially killing the process as result) – Alexei Levenkov May 04 '17 at 22:49

3 Answers3

10

I'm going to go out on a limb and say "it doesn't matter."

Boneheaded exceptions can be thrown directly (throw) or placed on the returned task (Task.FromException). Since they're boneheaded exceptions, they shouldn't ever be caught anyway, so it doesn't matter where they are thrown.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
5

When calling a method that returns a Task, some parts of it are executed synchronously (even if the implementing method is defined as async and has await calls in it.. until the first away, everything is synchronous by default).

So the outcome is the same for all options: throw immediately or return a Task that is already completed with an exception (only behaves the same if you await the call immediately) or mark a method async (which would expect you to have await calls but let's add it for completeness).

I'd go for throwing immediately because returning a Task may indicate that you "have started work" and the caller isn't required to await a Task so if the caller doesn't really care about when your Task completes (it doesn't even have a return value), the fact that the method isn't implemented won't show up.

Martin Ullrich
  • 94,744
  • 25
  • 252
  • 217
  • While I'm inclined to this ... it won't build bc there's no await – micahhoover Feb 21 '18 at 13:57
  • 1
    if you only throw in a method returning a task, don't define your method using the `async` keyword. – Martin Ullrich Feb 21 '18 at 16:49
  • 1
    In the framework we must use, the abstract method is async. We're going with `=> await Task.FromException(new NotImplementedException());` to satisfy both the compiler warning and the abstract method signature. – clarkitect Feb 11 '23 at 17:16
3

In your comment, you wrote:

we are trying to make an async version of NHibernate:)

That puts you in an unfortunate position: well-known libraries written by skilled programmers should be well-written to protect against accidental misuse (including misuse through copy/paste) by less-skilled programmers.

There are enough people who expect code such as await Task.WhenAll(a(), b(), c()) to work even if one of the asynchronous operations fails, that I'd say your first option shouldn't even be an option. If b() throws an exception synchronously, then a()'s returned task gets ignored, and c() doesn't get called.

I agree with Stephen Cleary's answer saying that NotImplementedException is, as he puts it, a boneheaded exception where it doesn't matter, as it should never end up in production code anyway. However, you write:

the question applies to the NotSupportedException and InvalidOperationException as well.

These are not necessarily boneheaded exceptions. These could end up in production code.

Your second option avoids that problem.

Your second option does have an additional problem: it doesn't throw an exception. Since no exception is actually thrown, you're hindering debugging: when something's going wrong, it's very useful to have the option in your debugger of breaking at the point where the exception is thrown rather than where it's caught.

I suggested also considering

public async override Task XAsync() {
  throw new NotImplementedException();
}

which you discarded because of the overhead associated with creating a state machine. I think this is not a valid reason to discard it. This is code where performance is not relevant, this is code that only handles error cases. I suggested it because in general, I'm in favour of using async/await over manipulating tasks directly, because it's so much easier to catch silly mistakes, because the time saved in development time is high enough to be worth it.

I understand why you don't want to go with this option, but I personally still would. It avoids the drawback of your first option. It avoids the drawback of your second option. Its own drawback, the slightly slower performance, is in my experience less likely to become a problem than the other two.

Out of the other two, hopefully the details of the drawbacks help you make a well-informed decision.