11

Visual Studio emits a warning for this code ('because this call is not awaited, execution of the current method continues before the call is completed').

static void Main(string[] args)
{
    FireAndForget(); // <-- Warning CS4014
    // Do something else.
}

static async Task FireAndForget()
{
    // Do something (cannot throw).
}

My understanding is that it is OK not to wait for the task in this particular case because FireAndForget will never throw an exception.

Instead of disabling the warning with a pragma, I was considering changing the return type of FireAndForget from Task to void. That effectively silences the compiler.

static async void FireAndForget() // <-- Task changed to void
{
    // Do something (cannot throw).
}

However, according to Stephen Cleary, 'async void' methods should be avoided so I am not quite sure what to do.

Is it OK to have a 'async void' method if the method is not designed to be awaitable in the first place and if no exception will be thrown?

ZunTzu
  • 7,244
  • 3
  • 31
  • 39
  • *the method is not designed to be awaitable in the first place* What does that mean? What does `FireAndForget` actually do? – Yuval Itzchakov Sep 24 '15 at 16:12
  • 3
    Why the heck was this downvoted + voted for closure? – ken2k Sep 24 '15 at 16:15
  • @ken2k I didn't vote to close but I can see where this would be seen as an opinion based question with no objectively correct answer. Maybe "Is it OK" could be rephrased to "What are the advantages and disadvantages". – pseudocoder Sep 24 '15 at 16:31
  • @Yuval Itzchakov just assume that FireAndForget does some IO and takes care of handling all errors. Even better: imagine that FireAndForget is a method written for .NET 4.0 which is being ported to .NET 4.5. – ZunTzu Sep 25 '15 at 08:47

4 Answers4

15

It's extremely rare to have a true fire-and-forget operation; that is, an operation where:

  • No one cares when it completes.
  • No one cares if it completes.
  • No one cares if it throws an exception.

Particularly with the last of these; most so-called "fire-and-forget" operations are not actually fire-and-forget because some action needs to be taken if it doesn't succeed.

That said, there are a few situations where a true fire-and-forget is applicable.

I prefer to use async Task and avoid the compiler warning by assigning the task to an otherwise unused variable:

var _ = FireAndForget();

async Task methods are more reusable and testable than async void methods.

However, I wouldn't throw a fit if a developer on my team just used async void instead.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I find it hard to believe it should be that rare. Without fire-and-forget methods wouldn't 'async' become viral and propagate to your Main method (where you would 'Wait')? – ZunTzu Sep 25 '15 at 08:22
  • Stephen, I have another question. I have tried assigning the task to a variable like in your example. Indeed it silences the compiler, but the task is still not Waited for. Shouldn't the compiler also emit a warning in that case? – ZunTzu Sep 25 '15 at 09:00
  • 1
    @ZunTzu: 1) Yes; that's how most (>98%) async programs work. 2) The compiler does its best to warn but it's not (cannot be) perfect. – Stephen Cleary Sep 25 '15 at 12:32
  • OK, thanks. By the way I just found out about [TplExtensions.Forget](https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.threading.tplextensions.forget.aspx). The specification says 'Useful for fire-and-forget calls to asynchronous methods within asynchronous methods'. Do they actually mean '...within **synchronous** methods'? – ZunTzu Sep 25 '15 at 13:24
  • 2
    @ZunTzu No, they don't mean that. It's typically wrong to be calling an asynchronous method from a synchronous method *at all*. The solution is typically not to fire and forget, but rather to not do it in the first place. And as to the question in your first comment, in most asynchronous programs you'll have a message loop (or some comparable concept) in the program at the top level of all of the asynchrony where it processes posted messages than can then continue to fire of additional asynchronous operations. – Servy Sep 25 '15 at 13:37
  • @Servy I don't understand. Would the body of your TopLevelLoop method use 'await'? If yes how would you avoid having to mark TopLevelLoop as 'async'? – ZunTzu Sep 25 '15 at 13:47
  • @ZunTzu You should basically never be actually writing your own message loop. You should virtually always be using something that will create one for you. Winforms, WPF, ASP, all of these platforms handle the message loop for you, and simply expose means for you to perform asynchronous messages. – Servy Sep 25 '15 at 13:50
  • I am accepting Stephen's answer, not because of his claim that true fire-and-forget operations should be extremely rare, but because of the trick of assigning the task to an unused variable. His article on [async MVVM](https://msdn.microsoft.com/en-us/magazine/dn605875.aspx) is a great illustration of that trick, and also acknowledges the existence of legitimate uses for fire-and-forget async operations. ;-) – ZunTzu Sep 25 '15 at 15:26
2

Is it OK to have a 'async void' method if the method is not designed to be awaitable in the first place and if no exception will be thrown?

Although it may be "OK" to do so, I would still encourage you to make the method async Task. Even though you are a 100 percent sure this method won't throw, and isn't ment to be awaited, you never know how it might end up getting used. You're now fully aware of what the consequences of using async void are, but if there's a slight chance that someone might need to use this in the future, then you're better of putting a nice comment on why this Task isn't being awaited instead of going with the easy path of making this void.

Don't let the compiler warnings worry you, I would say worry about the correctness and the effects this may have on your codebase.

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • Changing 'void' to 'Task' just because the method is 'async' feels wrong. Here is why. The only purpose of 'async' is to unlock the possibility to use 'await' in the body of the method. My opinion is that it means 'async' is not part of the external contract of the method. Changing 'void' to 'Task' on the contratry changes that external contract. – ZunTzu Sep 25 '15 at 08:42
  • @ZunTzu But it exposes the true operation of the method. When I see a `void` returning method, I safely assume it's synchronous. Invoking such a method which completes but it's underlying operation doesn't is way more confusing, and possibly lying to the caller. – Yuval Itzchakov Sep 25 '15 at 08:48
  • as a general rule the caller cannot tell if I launch a worker thread or if I call a web service. Why would the caller need to know if I use 'await'? It seems to me like a violation of the encapsulation principle. – ZunTzu Sep 25 '15 at 09:17
  • 2
    @ZunTzu The caller knows nothing about `async`, and he doesn't care. But when you expose a `Task`, you actually expose a *promise*, which tells the caller you're executing some work which will complete in the future. He need not know the implementation detail of how you're doing your work. By exposing a `void` returning method, you leave no control to the calling code. – Yuval Itzchakov Sep 25 '15 at 09:29
1

Sometimes you want to fire and forget, however you should always want to make it explicit for someone reading your code. I find the "_" notation not explicit enough, so here's how I do it, with an extension method:

public static class TaskExtensions()
{
    public static void InvokeAndIgnore(this Task fireAndForgetTask)
    {
        // deliberately do nothing; used to suppress warning
    }
}

You can use it as follows:

worker.AttemptCloseAsync().InvokeAndIgnore();
WolfRevokCats
  • 315
  • 1
  • 9
0

One potential problem point is that it will now be impossible to tell if the code threw an exception or not. So if you have unit tests in place to detect these, the unit tests will never work.

Classic example from MSDN site:

private async void ThrowExceptionAsync()
{
  throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
  try
  {
    ThrowExceptionAsync();
  }
  catch (Exception)
  {
    // The exception is never caught here!
    throw;
  }
}

http://haacked.com/archive/2014/11/11/async-void-methods/

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Instead of using async void, if this is a specific edge case, how about using pragma statements?

#pragma warning disable CS-4014
... your code here ...
#pragma warning restore CS-4014

This way you can tune out the static noise.

HTH...

code4life
  • 15,655
  • 7
  • 50
  • 82
  • I am aware of that limitation. That's why I explicitly wrote FireAndForget cannot throw an exception. – ZunTzu Sep 25 '15 at 08:38
  • Yeah, the specific intended usage of `async void` is really for event handlers, so any outside usage should be avoided, IMHO. Use pragmas instead. – code4life Sep 25 '15 at 13:22