388

Preface: I'm looking for an explanation, not just a solution. I already know the solution.

Despite having spent several days studying MSDN articles about the Task-based Asynchronous Pattern (TAP), async and await, I'm still a bit confused about some of the finer details.

I'm writing a logger for Windows Store Apps, and I want to support both asynchronous and synchronous logging. The asynchronous methods follow the TAP, the synchronous ones should hide all this, and look and work like ordinary methods.

This is the core method of asynchronous logging:

private async Task WriteToLogAsync(string text) { StorageFolder folder = ApplicationData.Current.LocalFolder; StorageFile file = await folder.CreateFileAsync("log.log", CreationCollisionOption.OpenIfExists); await FileIO.AppendTextAsync(file, text, Windows.Storage.Streams.UnicodeEncoding.Utf8); } 

Now the corresponding synchronous method...

Version 1:

private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.Wait(); } 

This looks correct, but it does not work. The whole program freezes forever.

Version 2:

Hmm.. Maybe the task was not started?

private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.Start(); task.Wait(); } 

This throws InvalidOperationException: Start may not be called on a promise-style task.

Version 3:

Hmm.. Task.RunSynchronously sounds promising.

private void WriteToLog(string text) { Task task = WriteToLogAsync(text); task.RunSynchronously(); } 

This throws InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

Version 4 (the solution):

private void WriteToLog(string text) { var task = Task.Run(async () => { await WriteToLogAsync(text); }); task.Wait(); } 

This works. So, 2 and 3 are the wrong tools. But 1? What's wrong with 1 and what's the difference to 4? What makes 1 cause a freeze? Is there some problem with the task object? Is there a non-obvious deadlock?

4
  • 1
    Any luck getting an explanation elsewhere? The answers below really don't provide insight. I'm actually using .net 4.0 not 4.5/5 so I can't use some of the operations but running into the same issues. Commented Mar 29, 2013 at 20:00
  • 3
    @amadib, ver.1 and 4 were explained in [rpvided answers. Ver.2 anв 3 try to start again already started task. Post your question. It is unclear how you can have .NET 4.5 async/await issues on .NET 4.0 Commented May 5, 2013 at 17:36
  • 2
    Version 4 is is the best option for Xamarin Forms. We tried out rest of the options and not worked and experienced deadlocks in all cases Commented Jul 15, 2016 at 11:56
  • Thanks! Version 4 worked for me. But does it still run asynchronously? I'm assuming so because the async keyword is there. Commented Feb 21, 2017 at 13:23

5 Answers 5

236

The await inside your asynchronous method is trying to come back to the UI thread.

Since the UI thread is busy waiting for the entire task to complete, you have a deadlock.

Moving the async call to Task.Run() solves the issue.
Because the async call is now running on a thread pool thread, it doesn't try to come back to the UI thread, and everything therefore works.

Alternatively, you could call StartAsTask().ConfigureAwait(false) before awaiting the inner operation to make it come back to the thread pool rather than the UI thread, avoiding the deadlock entirely.

Sign up to request clarification or add additional context in comments.

6 Comments

+1. Here is one more explanation -Await, and UI, and deadlocks! Oh my!
The ConfigureAwait(false) is the appropriate solution in this case. Since it has no need to call the callbacks in the captured context, it shouldn't. Being an API method it should handle it internally, rather than forcing all of the callers to move out of the UI context.
@Servy Am asking since you mentioned ConfigureAwait. I am using .net3.5 and i had to remove configure await cos it was not available in the async library i was using. How do i write my own or is there another way of awaiting my async call. Cos my method hangs too. I dont have Task But not Task.Run. This shoud probably be a question on its own.
@flexxxit: You should use Microsoft.Bcl.Async.
Here's the updated link: Await, and UI, and deadlocks! Oh my!
|
55

Calling async code from synchronous code can be quite tricky.

I explain the full reasons for this deadlock on my blog. In short, there's a "context" that is saved by default at the beginning of each await and used to resume the method.

So if this is called in an UI context, when the await completes, the async method tries to re-enter that context to continue executing. Unfortunately, code using Wait (or Result) will block a thread in that context, so the async method cannot complete.

The guidelines to avoid this are:

  1. Use ConfigureAwait(continueOnCapturedContext: false) as much as possible. This enables your async methods to continue executing without having to re-enter the context.
  2. Use async all the way. Use await instead of Result or Wait.

If your method is naturally asynchronous, then you (probably) shouldn't expose a synchronous wrapper.

2 Comments

I need to execute an Async task in a catch() which does not support async how would I do this and prevent a fire and forget situation.
@Zapnologica: await is supported in catch blocks as of VS2015. If you're on an older version, you can assign the exception to a local variable and do the await after the catch block.
8

Here is what I did

private void myEvent_Handler(object sender, SomeEvent e) { // I dont know how many times this event will fire Task t = new Task(() => { if (something == true) { DoSomething(e); } }); t.RunSynchronously(); } 

working great and not blocking UI thread

1 Comment

I tried this Code but not works. can I called method Synchronously with tis code ??
1

For me actually the best working solution:

AsyncMethod(<params>).ConfigureAwait(true).GetAwaiter().GetResult(); 

Works also on UI-Content without blocking and dispatcher problems, and also from CTOR's.

2 Comments

You can omit ConfigureAwait(true) as true is a default parameter
You can omit the ConfigureAwait regardless of true or false, because the purpose of this method is to configure the await. Since there is no await, there is nothing to configure, so the ConfigureAwait has no effect.
0

With small custom synchronization context, sync function can wait for completion of async function, without creating deadlock. Here is small example for WinForms app.

Imports System.Threading Imports System.Runtime.CompilerServices Public Class Form1 Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load SyncMethod() End Sub ' waiting inside Sync method for finishing async method Public Sub SyncMethod() Dim sc As New SC sc.WaitForTask(AsyncMethod()) sc.Release() End Sub Public Async Function AsyncMethod() As Task(Of Boolean) Await Task.Delay(1000) Return True End Function End Class Public Class SC Inherits SynchronizationContext Dim OldContext As SynchronizationContext Dim ContextThread As Thread Sub New() OldContext = SynchronizationContext.Current ContextThread = Thread.CurrentThread SynchronizationContext.SetSynchronizationContext(Me) End Sub Dim DataAcquired As New Object Dim WorkWaitingCount As Long = 0 Dim ExtProc As SendOrPostCallback Dim ExtProcArg As Object <MethodImpl(MethodImplOptions.Synchronized)> Public Overrides Sub Post(d As SendOrPostCallback, state As Object) Interlocked.Increment(WorkWaitingCount) Monitor.Enter(DataAcquired) ExtProc = d ExtProcArg = state AwakeThread() Monitor.Wait(DataAcquired) Monitor.Exit(DataAcquired) End Sub Dim ThreadSleep As Long = 0 Private Sub AwakeThread() If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume() End Sub Public Sub WaitForTask(Tsk As Task) Dim aw = Tsk.GetAwaiter If aw.IsCompleted Then Exit Sub While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False If Interlocked.Read(WorkWaitingCount) = 0 Then Interlocked.Increment(ThreadSleep) ContextThread.Suspend() Interlocked.Decrement(ThreadSleep) Else Interlocked.Decrement(WorkWaitingCount) Monitor.Enter(DataAcquired) Dim Proc = ExtProc Dim ProcArg = ExtProcArg Monitor.Pulse(DataAcquired) Monitor.Exit(DataAcquired) Proc(ProcArg) End If End While End Sub Public Sub Release() SynchronizationContext.SetSynchronizationContext(OldContext) End Sub End Class 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.