How does the async / await return callchain call function work?

I recently had a situation where I had an ASP.NET WebAPI controller that had to execute two web requests to another REST service inside its action method. I wrote my code so that the functionality was divided purely into separate methods, which looked something like this:

public class FooController : ApiController { public IHttpActionResult Post(string value) { var results = PerformWebRequests(); // Do something else here... } private IEnumerable<string> PerformWebRequests() { var result1 = PerformWebRequest("service1/api/foo"); var result = PerformWebRequest("service2/api/foo"); return new string[] { result1, result2 }; } private string PerformWebRequest(string api) { using (HttpClient client = new HttpClient()) { // Call other web API and return value here... } } } 

Since I used HttpClient , all web requests had to be asynchronous. I never used async / await, so I started naively adding keywords. I first added the async to the PerformWebRequest(string api) method, but then the caller complained that the PerformWebRequests() method must be async too to use await . So I did it async , but now the caller of this method should be async too, etc.

What I want to know is how far down all the async should be marked down the rabbit hole? Undoubtedly, there will come a time when something should start synchronously, and in which case, how is it handled safely? I already read that calling Task.Result is a bad idea because it can cause deadlocks.

+8
c # asynchronous task-parallel-library async-await
source share
2 answers

What I want to know is how far down the rabbit hole should all be labeled async to just work? Of course, there will come a time when something must be done synchronously

No, there should not be a point where something works synchronously, and that is what everything is asynchronous about. The phrase "async all the way" actually means all the way up the call stack.

When you process a message asynchronously, you allow your requests to process the loop requests while the truly asynchronous method is executed, because when you go deep into the hole for rabit, There is no thread .

For example, when you have an async click event handler:

 private async void Button_Click(object sender, RoutedEventArgs e) { await DoWorkAsync(); // Do more stuff here } private Task DoWorkAsync() { return Task.Delay(2000); // Fake work. } 

When the button is pressed, it is executed synchronously until the first await is pressed. Once pressed, the method returns control to the caller, which means that the button's event handler will free the user interface thread, which will free the message loop to process more requests at the same time.

The same goes for using HttpClient . For example, when you have:

 public async Task<IHttpActionResult> Post(string value) { var results = await PerformWebRequests(); // Do something else here... } private async Task<IEnumerable<string>> PerformWebRequests() { var result1 = await PerformWebRequestAsync("service1/api/foo"); var result = await PerformWebRequestAsync("service2/api/foo"); return new string[] { result1, result2 }; } private async string PerformWebRequestAsync(string api) { using (HttpClient client = new HttpClient()) { await client.GetAsync(api); } // More work.. } 

See how the async got to the main POST request processing method. Thus, while the asynchronous HTTP request is being processed by the network device driver, your thread returns to ASP.NET ThreadPool and can process more requests in the meantime.

The console application is a special case, because when the Main method terminates, if you do not spin a new foreground thread, the application will terminate. There you must make sure that if the only call is an asynchronous call, you will have to explicitly use Task.Wait or Task.Result . But in this case, by default, SynchronizationContext is ThreadPoolSynchronizationContext , where there is no way to cause a deadlock.

In conclusion, asynchronous methods should not be processed synchronously at the top of the stack , if there is no exotic use case (for example, a console application), they should be passed asynchronously completely allowing the thread to be freed whenever possible.

+13
source share

You need to "asynchronously to the end" to the very top of the call stack, where you reach a message loop that can handle all asynchronous requests.

0
source share

All Articles