1

I have an async function in C# as follows:

private static async Task AddEmoji(string emoji)
{
   ...
}

I have a situation where I'd like to call it but I don't want to wait for its result and I don't care if it works or not. What's the right way to go about this? I thought I could just call it without adding the async parameter:

AddEmoji("");

... which works, but VS gives me a CS4014 compile warning, so I guess that's not right. I then tried to create a Task for it:

Task.Run(() => await AddEmoji(""));

But that gives me an actual compile error:

Error CS4034 The 'await' operator can only be used within an async lambda expression. Consider marking this lambda expression with the 'async' modifier.

What's the right way to go about this?

user1438038
  • 5,821
  • 6
  • 60
  • 94
Chris Rae
  • 5,627
  • 2
  • 36
  • 51
  • Have you tried to call it without await: Task.Run(() => AddEmoji("")); – Nikola Babic Mar 14 '19 at 16:35
  • re the second attempt - you probably need `Task.Run(async () => await AddEmoji(""));` - however, you might just be able to drop the task on the floor... `GC.KeepAlive(somethingYouDoNotCareAbout)` works well in most cases (the call to `GC.KeepAlive` is actually just an opaque method that does nothing) or just suppress the warning! – Marc Gravell Mar 14 '19 at 16:35
  • 1
    If you don't care if it works or not, why bother calling it in the first place? – DavidG Mar 14 '19 at 16:39
  • Hi all - if I call @NikolaBabic's or Marc's suggestion, I get a CS4014 (consider applying the "await" operator) warning. It's not that I entirely don't care if it works - I just don't want to await for it to return or know whether it worked or not, because if it fails there's nothing I'm particularly going to do differently. – Chris Rae Mar 14 '19 at 16:52
  • @ChrisRae `#pragma warning disable CS4014` ... `#pragma warning restore CS4014` :) – Marc Gravell Mar 14 '19 at 16:56
  • @MarcGravell Yeah, that's going to be my plan if it turns out this is indeed the best way to go about it. – Chris Rae Mar 14 '19 at 16:56
  • @ChrisRae I'll reiterate DavidG's point because it's so important. Why do you want to call this code if it doesn't matter if it works or not?! If it doesn't matter, don't do it. If it does matter, then wait for it to return, or have it run in the background. – mason Mar 14 '19 at 16:56
  • 1
    Note that just dropping a Task on the floor prior to .NET 4.5 is a very bad idea. If the Task contains an exception, the Task's finalizer will rethrow it. This was changed in 4.5. – canton7 Mar 14 '19 at 16:58
  • @mason sorry I'm maybe not phrasing this very well - I do want to run it in the background. I do not, however, want to know when it finishes or what it returned. – Chris Rae Mar 14 '19 at 17:23
  • Do you care that it completes without error? – mason Mar 14 '19 at 17:24

3 Answers3

1

In your Task.Run(), the await doesn't go inside the Func<>. You would normally await on the Task itself (put the await before the Task.Run()), but I think in your case, you can omit the await in order to not wait and ignore the results:

Task.Run(() => AddEmjoi(""))
Cindy K.
  • 128
  • 2
  • 10
  • 2
    This grabs a new ThreadPool thread just to run the first bit of `AddEmoji`, which is unnecessarily expensive – canton7 Mar 14 '19 at 16:50
  • I get a CS4014 (consider applying the "await" operator) if I do this. It works, but obviously it's not best practise as I'm getting the warning. So I'm wondering what the "right" thing to do is. Also I agree with @canton7 - it seems like using Task.Run here just creates an extra layer of stuff that I'm not using. – Chris Rae Mar 14 '19 at 16:54
0

I don't want to wait for its result and I don't care if it works or not.

This is extremely unusual; the first thing to check is this assumption, that you literally don't need to know when it completes (or if it completes), and that you're OK with silently ignoring all exceptions. This also means that when your app exits, you're OK with this work never getting done (and again, you won't know because exceptions are ignored) - bearing in mind that in hosted scenarios like ASP.NET, you don't control when your app exits.

That said, you can do "fire and forget" as such:

var _ = AddEmoji("");
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • I hit this situation fairly regularly. Consider 1) a method which asynchronously reads data from a stream, in an infinite loop, publishing frames that it reads. This method never returns, but I want my application to crash if it throws an unexpected exception; 2) A `System.Threading.Timer` callback, which does an asynchronous operation and has its own error handling; 3) A state machine's state entry handler, which does some asynchronous work, and fires a suitable event on the state machine when that works succeeds or fails. I've yet to find a better approach than `async void` for these cases. – canton7 Mar 15 '19 at 10:58
  • @canton7: That first example is *not* "fire and forget", because you *want* to detect errors. The second example is a good use case for `async void` - note that it's an *event*, which is exactly why `async void` was added to the language in the first place. The third example is more dubious; it seems to me that a better design may be possible, but even with that example you're talking about a "handler" - i.e., a logical event, even if it's not a literal event, so `async void` would be acceptable there, too. – Stephen Cleary Mar 15 '19 at 13:05
  • Thanks for your response. It's hard to find good approaches to these cases, following "`async void` should never be used". In the first example, the method containing the infinite loop typically has its own error handling, but unhandled exceptions should bring down the application (e.g. https://git.io/fje5z). In that case, `async void` is a good fit as well, surely? Since unhandled exceptions are re-thrown on the captured SynchronizationContext / the ThreadPool, which has the desired effect. Agreed on the interpretation of 2, and I'm still searching for a better design on 3! – canton7 Mar 15 '19 at 13:15
  • @canton7: I'm the originator of the "avoid `async void`" mantra. As described in my [original article on the subject](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx), the guideline is to avoid `async void`, **unless** you're using it for an event handler (or event-like semantics). So I'm not saying it should *never* be used - it's just a common error for `async` newbies to use `async void` when they mean to use `async Task`. – Stephen Cleary Mar 15 '19 at 13:53
-5

This does not apply to asp.net -- async void methods are not permitted in asp.net applications. Console, WPF, Winforms, etc, applications are fine however. See the comments for the full discussion.

The best thing to do is to await it inside an async void method.

The problem with just writing AddEmoji("");, is: what happens if it fails? If an exception is thrown inside AddEmoji, then that exception is bundled up inside the Task which AddEmoji returned. However, because you threw that Task away, you'd never know. So AddEmoji can fail in a bad way, but you'd never know. This is why the compiler is giving you an error.

However, async void methods avoid that. If an exception is thrown inside an async void method, then that exception is re-thrown on the ThreadPool, which will bring down your application. You'll definitely know that something went wrong!

People advise against using async void methods, but they are perfect for fire-and-forget situations. The advice stems from people trying to use async void methods when they do want to know when the operation completed.

public async void AddEmojiImpl(string emoji) => await AddEmoji(emoji);

Or

public async void AddEmojiImpl(string emoji)
{
    try 
    {
        await AddEmojiImpl(emoji);
    }
    catch (EmojiAddException e)
    {
        // ...
    }
}

Local functions are particularly useful for this.

canton7
  • 37,633
  • 3
  • 64
  • 77
  • 5
    "but they are perfect for fire-and-forget situations. " - No, I strongly disagree - this is a **VERY BAD THING**, and the *reason* is : there are frameworks (including MVC) where this is *actively blocked* - meaning: it causes an exception (due to the sync-context choosing to not allow it). I agree with you *semantically*, but *in reality* : it is a *really really bad thing to get into the habit of doing*. If it doesn't bite you today, it could start failing unexpectedly later due to internal framework changes. I've had to re-issue libraries because of doing exactly this. – Marc Gravell Mar 14 '19 at 16:37
  • 1
    @MarcGravell expand on what exactly you mean by "actively blocked" please? Do you mean that the captured SynchronizationContext won't allow the continuation to be posted to it? That's not something I've come across - do you have a source for that? Surely exactly that same risk is present in the "just call it and use `GC.KeepAlive` to suppress the warning" approach you advocated in the comments, since `AddEmoji` can post back to that SynchronizationContext at any point? – canton7 Mar 14 '19 at 16:39
  • 2
    by "actively blocked", I mean: it will literally throw an exception when you do this; the sync-context does this - the error in the case of ASP.NET is https://i.stack.imgur.com/uad2C.png - this happens simply when **calling** an `async void` method - getting raised [from here, specifically](https://referencesource.microsoft.com/#system.web/AspNetSynchronizationContext.cs,108) (when `_state.AllowVoidAsyncOperations` is `false`). That do? – Marc Gravell Mar 14 '19 at 16:50
  • when you call `GC.KeepAlive` with a `Task` (to drop it on the floor while keeping the compiler happy), it isn't triggering the `async void` scenario that the sync-context is **explicitly looking for** (to block) - because it is now an `async Task` – Marc Gravell Mar 14 '19 at 16:51
  • 1
    @MarcGravell Interesting. Perfect, that's exactly what I was after. Thanks! That's the first actual solid argument I've seen against `async void`. I'll update my answer. – canton7 Mar 14 '19 at 16:55
  • I guess something like `ContinueWith` and `ThreadPool.QueueUserWorkItem` is probably the way to go? – canton7 Mar 14 '19 at 16:59
  • or you could just QUWI and not touch Task at all? – Marc Gravell Mar 14 '19 at 17:13