I came across some best practices for asynchronous programming using c#\'s async/await keywords (I\'m new to c# 5.0).
One of the advices gi
Another main point is that you should not block on Tasks, and use async all the way down to prevent deadlocks. Then it will be all asynchronous not synchronous blocking.
public async Task<ActionResult> ActionAsync()
{
var data = await GetDataAsync();
return View(data);
}
private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Take a look at this example, Stephen has a clear answer for you:
So this is what happens, starting with the top-level method (
Button1_Clickfor UI /MyController.Getfor ASP.NET):
The top-level method calls
GetJsonAsync(within the UI/ASP.NET context).
GetJsonAsyncstarts the REST request by callingHttpClient.GetStringAsync(still within the context).
GetStringAsyncreturns an uncompletedTask, indicating the REST request is not complete.
GetJsonAsyncawaits theTaskreturned byGetStringAsync. The context is captured and will be used to continue running theGetJsonAsyncmethod later.GetJsonAsyncreturns an uncompletedTask, indicating that theGetJsonAsyncmethod is not complete.The top-level method synchronously blocks on the
Taskreturned byGetJsonAsync. This blocks the context thread.... Eventually, the REST request will complete. This completes the
Taskthat was returned byGetStringAsync.The continuation for
GetJsonAsyncis now ready to run, and it waits for the context to be available so it can execute in the context.Deadlock. The top-level method is blocking the context thread, waiting for
GetJsonAsyncto complete, andGetJsonAsyncis waiting for the context to be free so it can complete. For the UI example, the "context" is the UI context; for the ASP.NET example, the "context" is the ASP.NET request context. This type of deadlock can be caused for either "context".
Another link you should read: Await, and UI, and deadlocks! Oh my!
I was just fiddling with this issue again in an ASP.NET MVC project. When you want to call async methods from a PartialView, you're not allowed to make the PartialView async. You'll get an exception if you do.
You can use the following simple workaround in the scenario where you want to call an async method from a sync method:
SynchronizationContextSynchronizationContextExample:
public ActionResult DisplayUserInfo(string userName)
{
// trick to prevent deadlocks of calling async method
// and waiting for on a sync UI thread.
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
// this is the async call, wait for the result (!)
var model = _asyncService.GetUserInfo(Username).Result;
// restore the context
SynchronizationContext.SetSynchronizationContext(syncContext);
return PartialView("_UserInfo", model);
}
GetDataAsync().Result; will run when the task returned by GetDataAsync() completes, in the meantime it blocks the UI threadreturn result.ToString()) is queued to the UI thread for executionGetDataAsync() will complete when its queued continuation is runThe deadlock can be broken by provided alternatives to avoid Fact 1 or Fact 2.
var data = await GetDataAsync(), which allows the UI thread to keep runningvar data = Task.Run(GetDataAsync).Result, which will post the continuation to the sync context of a threadpool thread. This allows the task returned by GetDataAsync() to complete. This is explained really well in an article by Stephen Toub, about half way down where he uses the example of DelayAsync().
A work around I came to is to use a Join extension method on the task before asking for the result.
The code look's like this:
public ActionResult ActionAsync()
{
var task = GetDataAsync();
task.Join();
var data = task.Result;
return View(data);
}
Where the join method is:
public static class TaskExtensions
{
public static void Join(this Task task)
{
var currentDispatcher = Dispatcher.CurrentDispatcher;
while (!task.IsCompleted)
{
// Make the dispatcher allow this thread to work on other things
currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
}
}
}
I'm not enough into the domain to see the drawbacks of this solution (if any)