Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use Task.Run() in synchronous method to avoid deadlock waiting on async method?

UPDATE The purpose of this question is to get a simple answer about Task.Run() and deadlocking. I very much understand the theoretical reasoning for not mixing async and sync, and I take them to heart. I'm not above learning new things from others; I seek to do that whenever I can. There's just times when all a guy needs is a technical answer...

I have a Dispose() method that needs to call an async method. Since 95% of my code is async, refactoring isn't the best choice. Having an IAsyncDisposable (among other features) that's supported by the framework would be ideal, but we're not there yet. So in the mean time, I need to find a reliable way to call async methods from a synchronous method without deadlocking.

I'd prefer not to use ConfigureAwait(false) because that leaves the responsibility scattered all throughout my code for the callee to behave a certain way just in case the caller is synchronous. I'd prefer to do something in the synchronous method since it's the deviant bugger.

After reading Stephen Cleary's comment in another question that Task.Run() always schedules on the thread pool even async methods, it made me think.

In .NET 4.5 in ASP.NET or any other synchronization context that schedules tasks to the current thread / same thread, if I have an asynchronous method:

private async Task MyAsyncMethod() {     ... } 

And I want to call it from a synchronous method, can I just use Task.Run() with Wait() to avoid deadlocks since it queues the async method the the thread pool?

private void MySynchronousMethodLikeDisposeForExample() {     // MyAsyncMethod will get queued to the thread pool      // so it shouldn't deadlock with the Wait() ??     Task.Run((Func<Task>)MyAsyncMethod).Wait(); } 
like image 415
MikeJansen Avatar asked Feb 03 '15 18:02

MikeJansen


People also ask

What happens if you dont await an async method?

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.

What is async await and task?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.

Can we use async without task?

Implementing a method with the async keyword without using await raises a compiler warning. You could remove the async keyword and use "return Task.


2 Answers

It seems you understand the risks involved in your question so I'll skip the lecture.

To answer your actual question: Yes, you can just use Task.Run to offload that work to a ThreadPool thread which doesn't have a SynchronizationContext and so there's no real risk for a deadlock.

However, using another thread just because it has no SC is somewhat of a hack and could be an expensive one since scheduling that work to be done on the ThreadPool has its costs.

A better and clearer solution IMO would be to simply remove the SC for the time being using SynchronizationContext.SetSynchronizationContext and restoring it afterwards. This can easily be encapsulated into an IDisposable so you can use it in a using scope:

public static class NoSynchronizationContextScope {     public static Disposable Enter()     {         var context = SynchronizationContext.Current;         SynchronizationContext.SetSynchronizationContext(null);         return new Disposable(context);     }      public struct Disposable : IDisposable     {         private readonly SynchronizationContext _synchronizationContext;          public Disposable(SynchronizationContext synchronizationContext)         {             _synchronizationContext = synchronizationContext;         }          public void Dispose() =>             SynchronizationContext.SetSynchronizationContext(_synchronizationContext);     } } 

Usage:

private void MySynchronousMethodLikeDisposeForExample() {     using (NoSynchronizationContextScope.Enter())     {         MyAsyncMethod().Wait();     } } 
like image 56
i3arnon Avatar answered Sep 20 '22 17:09

i3arnon


This is my way of avoiding deadlock when I have to call async method synchronously and the thread can be UI thread:

    public static T GetResultSafe<T>(this Task<T> task)     {         if (SynchronizationContext.Current == null)             return task.Result;          if (task.IsCompleted)             return task.Result;          var tcs = new TaskCompletionSource<T>();         task.ContinueWith(t =>         {             var ex = t.Exception;             if (ex != null)                 tcs.SetException(ex);             else                 tcs.SetResult(t.Result);         }, TaskScheduler.Default);          return tcs.Task.Result;     } 
like image 43
Dmitry Naumov Avatar answered Sep 19 '22 17:09

Dmitry Naumov