2

I am rewriting some of my component management to use async start methods. Sadly it looks like a call to an async method WITHOUT await, still does await the result?

Can anyone enlighten me?

I am calling:

public async Task StartAsync() {
    await DoStartProcessingAsync();
}

which in itself is calling a slow implementation of protected abstract Task DoStartProcessingAsync(); - slow because it dome some EF calls, then creates an appdomain etc. - takes "ages".

The actual call is done in the form:

x.StartAsync().Forget();

with "Forget" being a dummy function just to avoid the "no await" warning:

public static void Forget(this Task task) {
}

Sadly, this sequence - is waiting for the slow DoStartAsync method to complete, and I see no reason for that. I am quite old in C#, but quite new to async/await and I was under the impression that, unless I await for the async task - the method would complete. As such, I expected the call to StartAsyc().Forget() to return immediatly. INSTEAD stack trace shows the thread going all the way into the DoStartProcessingAsync() method without any async processing happening.

Anyone can enlighten me on my mistake?

Liam
  • 27,717
  • 28
  • 128
  • 190
TomTom
  • 61,059
  • 10
  • 88
  • 148
  • 3
    Can you show `DoStartProcessingAsync()`? Just to make sure it's not only called `Async` but really is awaiting something and not working synchronously. – René Vogt Feb 09 '16 at 10:34
  • I think this is less about async and more about processing in the background ... for example the `async Task Blah() { await Something(); }` should be just the same as just `Something()` - this does not magically make it happen in the background – Random Dev Feb 09 '16 at 10:36
  • 1
    `async` methods will run synchronously till it reaches first await. If there is no await, it runs completely synchronously. Can you post the code of `DoStartProcessingAsync`? – Sriram Sakthivel Feb 09 '16 at 10:36
  • That one is abou 2 pages of code. It is internally working synchronously - mostly because the slowest parts of it have no async method (i.e. creating an appdomain, loading assemblies into it). I could move some of the code to ahve async (there is some EF in there). But generally- the whole sense of that is from the idea that StartAsync starts a task that then in DoStartProcessingAsync is executing, regardless how it is implemented. The component in question has a status - so the call to Start would put it into "starting" and the end of the DoStartProcessing into Running. – TomTom Feb 09 '16 at 10:37
  • 1
    I'm surprised if you don't want to await you are making your `StartAsync` async and inside `await`ing `DoStartProcessingAsync()`. Why don't you just remove those particular keywords then do `var result = x.StartAsync()` and it should just work – Callum Linington Feb 09 '16 at 10:37
  • @SriramSakthivel But this is NOT what the warning says. If I take out the Forget it tells me... "Because this call is not awaited, execution of the method will continue BEFORE the called method has finished". – TomTom Feb 09 '16 at 10:38
  • @CallumLinington In this case the components act as some service. They go Immediately into a starting sate - initialization can take up to a minute before they switch to running. The whole concept is to return fast to the call to start them. – TomTom Feb 09 '16 at 10:39
  • 3
    @TomTom mere presence of `async` keyword doesn't automagically makes a method asynchronous. There needs to be a `await` keyword and the method you're awaiting should also be asynchronous. If you have time consuming parts in the method, then consider calling it in another thread i.e `await Task.Run(()=> DoStartProcessingAsync());` – Sriram Sakthivel Feb 09 '16 at 10:40
  • If you want a "fire and forget" type call your likely [better of using Tasks](http://stackoverflow.com/a/12804036/542251) – Liam Feb 09 '16 at 10:40
  • @TomTom That warning means that when the first await point is hit, your method will continue executing. It won't wait for the whole method (all await points) to complete. – Sriram Sakthivel Feb 09 '16 at 10:41
  • TomTom what @Liam posted is what I'm saying – Callum Linington Feb 09 '16 at 10:42
  • So, you mean I should kill StartAsync and push a Task.Run in Start to make sure it goes into a separate thread? will try that. put it up as an answer ;) – TomTom Feb 09 '16 at 10:46

1 Answers1

3

What your trying to achieve here is a fire and forget type mechanism. So async/await isn't really what you want. Your not wanting to await anything.

These are designed to free up threads for long running processes. Right now your returning a Task using an await and then "forgetting" it. So why return the Task at all?

Your freeing up the thread for the long running process, but your also queuing a process that ultimately does nothing (this is adding overhead that you could likely do without).

Simply doing this, would probably make more sense:

public void StartAsync() {
    Task.Run(() => DoStartProcessingAsync());
}

One thing to bear in mind is that your now using a ThreadPool thread not a UI thread (depending on what is actually calling this).

Liam
  • 27,717
  • 28
  • 128
  • 190
  • I have rewritten Start to use return Task.Run(DoStopProcessingAsync); return task - this way the starting method CAN wait for the execution to finish. Works as intended now. – TomTom Feb 09 '16 at 11:49
  • 1
    @Liam: Should use `Task.Run` and not the much more dangerous `Task.Factory.StartNew`. – Stephen Cleary Feb 09 '16 at 12:59
  • @StephenCleary [Task.run is just a wrapper for the Task.Factory.StartNew with a few switches](http://stackoverflow.com/a/29693430/542251). I don't really see why this add's much benefit? I'll add it for completeness but it's horses for courses, surely? It's also only .Net 4.5. – Liam Feb 09 '16 at 13:12
  • 2
    @Liam: Yes, but those switches are [really, *really* important to get right](http://blog.stephencleary.com/2013/08/startnew-is-dangerous.html). And when you don't specify them, they're wrong. – Stephen Cleary Feb 09 '16 at 13:13
  • I don't target .Net 4.5 currently so we don't use this. – Liam Feb 09 '16 at 13:17