49

I am trying to find the best practice for one of my project. It is a typical WPF application with a UI that displays a list of items and there is a data service that returns the result.

We are calling the service asynchronously so as to not block the UI. We have 2 options in front of us:

  1. Using Async await keywords This requires marking all the methods Async from button click all the way to service layer (class on client side that makes the http call to the server) and any method in between. This approach works fine other then the issue of propagating async everywhere

  2. Use awaiter and callback In this approach the UI client calls the service layer and passes a callback to the service layer, the service layer wraps the http call to the server in a task and use GetAwaiter().GetResult(), when the http call is finished it invokes the callback passed by the UI client. In this case no method has to marked async, but not really sure about the use of GetAwaiter()

    Task.Run(async () => //await http call, invoke callback).GetAwaiter().GetResult();

I am just trying to find out which is a better approach and if there are some issues with either approach that I should be aware of

vikas mittal
  • 595
  • 2
  • 6
  • 11
  • 2
    I'm pretty sure the second approach may lead to dead locks once in a while. I know because I've faced something alike when developing with Windows Universal. – Felype Dec 31 '15 at 17:32
  • There is also the question of exceptions. I know with a full async implementation, you get the `AggregateException` at the end, but I have no idea what happens to exceptions in the `GetAwaiter` method – Eris Dec 31 '15 at 17:33
  • @Eris When you await you don't get an `AggregateException`. You get the inner exception. The same happens with `GetResult`. – i3arnon Dec 31 '15 at 17:35

1 Answers1

62

You should use the async and await keywords all the way up, or you shouldn't use async at all.

Your second option is not really asynchronous. It's calling an asynchronous operation and blocking on it synchronously with task.GetAwaiter().GetResult(). On top of being very complicated it's not asynchronous and may lead to deadlocks.

i3arnon
  • 113,022
  • 33
  • 324
  • 344
  • 4
    Do you have some more background info on why it may cause to deadlocks ? I've experienced this as well, but I'm looking for some more in-depth info about why this happens or how this works. – Frederik Gheysels Feb 28 '17 at 07:38
  • 7
    @FrederikGheysels http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html – i3arnon Feb 28 '17 at 10:07
  • Sounds like great advice, except I'm stuck with this API from Azure Active Directory (TokenCache) which requires synchronous operations. You can't return from 'BeforeAccessNotification' until you're read the cache and you can't return from 'AfterAccessNotification' until you're written the cache. What's your advice when you're forced into a syncrhous API and need to access a file? – Quark Soup Mar 20 '17 at 11:40
  • @MikeDoonsebury "... or you shouldn't use async at all". – i3arnon Mar 20 '17 at 14:14
  • 3
    Right, so how does one call a function like FileIO.ReadBufferAsync without async? – Quark Soup Mar 20 '17 at 22:50
  • @MikeDoonsebury it would be better if you could use a different API, but if that's all there is all you can do is block it with GetAwaiter().GetResult(). – i3arnon Mar 21 '17 at 06:14
  • await returns the data async but doesn't return an bubbled up exceptions, GetAwaiter().GetResult() returns an exception is this correct? – TheWebGuy Feb 27 '18 at 15:32
  • 2
    @TheWebGuy Not sure what you're asking. But both `await` and `GetAwaiter().GetResult()` will rethrow the exception if the task was faulted. – i3arnon Feb 27 '18 at 19:58
  • 4
    There was asynchronous programming before the `async` keyword came into play. You would use `Task.ContinueWith` and provide a callback, just like is done ubiquitously in Javascript. It's exactly the same thing. `async` and `await` just make it easier to code. – Emperor Eto Nov 15 '18 at 16:25
  • @Quarkly - if at all possible, defer the *must be synchronous* action by moving it into a callback (or continuation) that occurs when the *asynchronous* task returns. Or to put it another way: can you re-arrange the code so that all *asynchronous* tasks complete, and only then perform the *synchronous* call? – ToolmakerSteve Nov 23 '19 at 19:27
  • i have converted everything to async and await, but now it is asking me to change my main method as well. I don't want to make my main method async and used MainAsync(args).GetAwaiter().GetResult(); to point of my async main method. How can i achieve synchronous operation with async operation as well, without causing starvation in thread pool? – Psychonaut007 May 02 '22 at 10:32
  • 1
    @Psychonaut007 starting with C# 7.1 you can make Main async: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-7.1/async-main – i3arnon May 03 '22 at 11:09