1

I have a class with an async method:

public static async Task GetData() { ... }

In the app framework I am using I need to start that process and forget about it when the app starts:

protected override void OnStart()
{
    await MyService.GetData();
}

I can't make OnStart async. How do I start it in a background task and forget about it?

svick
  • 236,525
  • 50
  • 385
  • 514
Ian Vink
  • 66,960
  • 104
  • 341
  • 555
  • Tasks run on background threads. What are you asking? Does `GetData` do a lot of synchronous processing before starting the asynchronous part? – Panagiotis Kanavos Feb 03 '15 at 10:57

2 Answers2

4

I can't make OnStart Async. How do I start it in a background task and forget about it?

Why not? Nothing prevents you from making it async. The async modifier doesn't affect the CLR method signature, i.e., you can override a void method and make it async:

abstract class AppBase
{
    protected abstract void OnStart();
}

class App: AppBase
{
    public static async Task GetData() { await Task.Delay(1); }

    protected override async void OnStart()
    {
        await GetData(); 
    }
}

This way, at least you'll see an exception if GetData throws, unlike what the other answer suggests.

Make sure you understand how async void methods and Task error handling work in general, this material may be helpful.

Some other problems with Task.Run( () => MyService.GetData() ):

  • as GetData is already asynchronous, there's very little sense in wrapping it with Task.Run. It's usually only done in a client-side UI app and only if GetData has a long-running synchronous part (before it hits its 1st await). Otherwise, you might as well just call GetData() without Task.Run and without await (which also would be a bad idea: in either case, you'd be doing a fire-and-forget call without observing possible exceptions).

  • Task.Run will start GetData on a random pool thread without synchronization content, which may be a problem for either a UI app or an ASP.NET app.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • I think this is a bad idea (here is [why](http://stackoverflow.com/a/28284243/885318)) – i3arnon Feb 02 '15 at 18:27
  • 1
    Why `await` it when you don't care when it finishes? Just invoke the method and ignore the `Task`. – Servy Feb 02 '15 at 18:41
  • @l3arnon, there is a link in my answer which goes into details about how `async void` methods work, including exceptions. Whether the OP wants to handle exceptions here (see Eric Lippert's [Vexing exceptions](http://blogs.msdn.com/b/ericlippert/archive/2008/09/10/vexing-exceptions.aspx)) or whether he wants to crash the process is up to him. The point is the exceptions wouldn't go unnoticed. – noseratio Feb 02 '15 at 18:41
  • @Servy, see my comment above. For a more detailed discussion, see [this](http://stackoverflow.com/q/22864367/1768303). – noseratio Feb 02 '15 at 18:42
  • @Noseratio The least desirable behavior is indeed to ignore exceptions, and just above it is to tear down the entire process. There are many ways to not ignore exceptions: 2 events, `try-catch` inside, continuations and crashing is the least pleasant and the most dangerous. – i3arnon Feb 02 '15 at 19:02
  • @l3arnon, feel free to down-vote if you really think what I wrote is a bad idea. I won't retaliate :) – noseratio Feb 02 '15 at 19:44
1

If you want to fire this async operation and forget about it all you need to do is invoke the method without awaiting the returned task:

protected override void OnStart()
{
    MyService.GetDataAsync();
}

However, since you're not observing the task you would never know if it completed successfully.

You should either keep a reference to the task and await it in a later time:

public Task _dataTask;
protected override void OnStart()
{
    _dataTask = MyService.GetDataAsync();
}

public Task AwaitInitializationAsync()
{
    return _dataTask;
}

Or add a continuation handling any exceptions:

protected override void OnStart()
{
    MyService.GetDataAsync().ContinueWith(t => 
    {
        try 
        { 
            t.Wait();
        }
        catch (Exception e)
        {
            // handle exceptions
        }
    });
}

You shouldn't use Task.Run as Noseratio explained, however using async void is much worse since an exception in an async void method (which isn't a UI event handler) would tear down the entire process*.

You can try to make the method async void while making sure there won't be any exceptions thrown inside it with a try-catch block:

protected override async void OnStart()
{
    try
    {
        await GetData(); 
    }
    catch (Exception e)
    {
        // handle e.
    }
}

But I would still recommend against it since even the chance of a complete crash is dangerous.


*You can get around that by registering an even handler for AppDomain.CurrentDomain.UnhandledException but this should be a last resort, not a best practice

Community
  • 1
  • 1
i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • Whether the OP wants to handle exceptions, or tear down the process, is up to him, see the comment to my answer. The least desirable behavior is to ignore exceptions of fire-and-forget tasks, which was my point. That said, if he does want to handle them, why use `ContinueWith` and not just `async void` with `await` inside `try/catch` inside? – noseratio Feb 02 '15 at 18:51
  • See [this comment](http://stackoverflow.com/questions/28257772/c-sharp-running-a-task-in-the-background-pcl#comment44923620_28259686) – i3arnon Feb 02 '15 at 19:15
  • Good to see you made an edit. I can hardly think of any use cases where `ContinueWith` would give any advantage over or couldn't be replaced with `async/await`. Especially, keeping in mind implications with error handling in continuations. – noseratio Feb 02 '15 at 19:41
  • @Noseratio I, of course, have no problem with `async-await` but `async void` was meant for UI event handlers and should generally be discouraged elsewhere. – i3arnon Feb 02 '15 at 21:41
  • I'd say, not only UI event handlers, but any kind of event handlers. Personally, I don't think it's a dogma. I also use it for `On`-like overridable notifications like the OP's `OnStart` and for fire-and-forget tasks as discussed [here](http://stackoverflow.com/q/22864367/1768303). – noseratio Feb 02 '15 at 22:27
  • @Noseratio well, not really. Since in other kinds of event handlers you don't necessarily have a `SynchronizationContext`. – i3arnon Feb 02 '15 at 22:30
  • I don't see why `SynchronizationContext` is relevant here. If there's none, exceptions from `async void` will still be thrown asynchronously on `ThreadPool`. – noseratio Feb 02 '15 at 22:34
  • @Noseratio That's exactly the point. That's what crashes the application. There's no difference between SC-less event handlers and just regular methods. – i3arnon Feb 02 '15 at 22:38
  • *There's no difference between SC-less event handlers and just regular methods* - Hmm, I don't understand this. Indeed, there's no difference between event handlers and regular methods on a thread without SC. As much as on a thread with SC. Event handler *is* a just regular method, it only has special signature - and only by convention. There's no special treatment given by C# or CLR to "event handler" methods. If a method is `async void`, the unhandled exception will crash the app regardless of SC, unless there is a global handler like `AppDomain.CurrentDomain.UnhandledException`. – noseratio Feb 02 '15 at 23:04
  • @Noseratio The difference is that when there's an SC the exception is posted to it, and the decision whether to crash or not is left to the SC ([here, for example, is an SC that handles exceptions instead of crashing](http://www.markermetro.com/2013/01/technical/handling-unhandled-exceptions-with-asyncawait-on-windows-8-and-windows-phone-8/)). When you don't have an SC you have no choice but to crash. – i3arnon Feb 03 '15 at 02:03
  • I appreciate how you care about `SynchronizationContext` here. You may want to change your `ContinueWith` solution to take this into account, too. As is, your error handling logic would run on `ThreadPool` rather than on the original SC. – noseratio Feb 03 '15 at 03:43
  • @Noseratio 1. I don't. I care about not crashing my application. 2. There's no SC to begin with. 3. What's the point of using the SC? The goal is not to let the exception be unhandled and `ContinueWith` does that with a `try-catch` block. What would the SC add? – i3arnon Feb 03 '15 at 07:03