Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Synchronously waiting for an async operation, and why does Wait() freeze the program here

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?

like image 708
Sebastian Negraszus Avatar asked Jan 23 '13 16:01

Sebastian Negraszus


People also ask

What happens when async method is not awaited?

The call to the async method starts an asynchronous task. However, because no Await operator is applied, the program continues without waiting for the task to complete. In most cases, that behavior isn't expected.

How does task Wait work?

Wait is a synchronization method that causes the calling thread to wait until the current task has completed. If the current task has not started execution, the Wait method attempts to remove the task from the scheduler and execute it inline on the current thread.


1 Answers

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.

like image 148
SLaks Avatar answered Oct 05 '22 07:10

SLaks