1

I've got a method which looks like this:

    static async Task<string> GetGooglePage(ProxyInfo proxy)
    {
        using (var m=new MyNetworkClass())
        {
            m.Proxy = proxy;
            return await m.GetAsync("http://www.google.com");
        }
    }

now I want to call it from method that is not async, and get the result.

The way I was trying to do it is like this:

        foreach (var proxy in proxys)
        {
            try
            {
                GetGooglePage(proxy.ToProxyInfo()).Wait();
            }
            catch
            {}
            lock (Console.Out)
            {
                Console.Write(current++ + "\r");
            }
        }

My problem is that sometimes GetGooglePage(proxy.ToProxyInfo()).Wait(); deadlocks (according to visual studio debugger, there is no stack beyond this call).

I don't want to use async all the way through to Main(). How do I correctly call GetGooglePage from synchronous code without risking a deadlock?

Arsen Zahray
  • 24,367
  • 48
  • 131
  • 224
  • I don't see any code which causes deadlock here. How does it happen? – Sriram Sakthivel Aug 31 '13 at 20:30
  • 1
    @SriramSakthivel The cause is in the question. `GetGooglePage`'s `return` doesn't get a chance to run until the main thread is available, but it won't become available, because it's waiting for `GetGooglePage` to finish. –  Aug 31 '13 at 20:31
  • Why are you `lock`ing in your example? With the way you have your code written, it's running in serial. – Matthew Aug 31 '13 at 20:32
  • @Matthew the lock for counter I copy pasted from a parallel.ForEach I was running. I was trying to benchmark the code and got tired watching the empty console `GetGooglePage` is not inside the lock – Arsen Zahray Aug 31 '13 at 20:38
  • @SriramSakthivel I run the code, and at some point it stops running. When I break all in VS, it shows current position at `GetGooglePage(proxy.ToProxyInfo()).Wait();` – Arsen Zahray Aug 31 '13 at 20:39
  • @hvd yeah, I think you're right. So how do I call the method correctly and avoid this? – Arsen Zahray Aug 31 '13 at 20:40
  • 2
    @ArsenZahray Don't `Wait` on async methods, It can cause deadlock as you've seen. use async/await chain. (Sometimes configuring task with `task.ConfigureAwait(false);` can help if you don't want to interact with UI thread) http://stackoverflow.com/questions/15021304/an-async-await-example-that-causes-a-deadlock – I4V Aug 31 '13 at 20:45
  • @hvd If that is the case it should deadlock always right? or am I missing something? – Sriram Sakthivel Aug 31 '13 at 20:46
  • @hvd so what you're saying is that Microsoft did not foresee a way to call async method from sync code? – Arsen Zahray Aug 31 '13 at 20:51
  • also, if I mark `Main` as async, I'm getting `an entry point cannot be marked with the 'async' modifier` – Arsen Zahray Aug 31 '13 at 20:53
  • @SriramSakthivel Good question. It's possible that `GetAsync` returns synchronously, that depends on how it's implemented. If `GetAsync` happens to return synchronously, so will `GetGooglePage`, and `Wait` will do nothing since the task has already finished. –  Aug 31 '13 at 21:00
  • @ArsenZahray No, that isn't what I'm saying. Clearly, they did, otherwise there would be no `Wait` method. It's just very easy to use it incorrectly. –  Aug 31 '13 at 21:04

2 Answers2

4

You're running into a deadlock that I describe on my blog.

There is no standard solution for blocking on an async method, because there is no solution that works in every situation. The only officially recommended solution is to use async all the way. Stephen Toub has a good summary of workarounds here.

One approach is to execute the async method in a background thread (via Task.Run) and then Wait on that. Disadvantages of this approach are: 1) it won't work for async methods that require a specific context (e.g., write to an ASP.NET response, or update a UI); 2) changing from a synchronized context to the unsynchronized thread pool context may introduce race conditions; and 3) it burns a thread just waiting for another thread.

Another approach is to execute the async method within a nested message loop. Disadvantages of this approach are: 1) the "nested message loop" is different for every platform (WPF vs. WinForms, etc); and 2) nested loops introduce reentrancy issues (which have been responsible for many, many bugs in the Win32 era).

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
2

The best option is not to do this: if you synchronously wait for an async method, there is no reason for the method to be async in the first place. So, what you should do (assuming you really want to or have to make the top level method synchronous) is to make all the methods synchronous.

If you can't do that, then you can prevent the deadlock by using ConfigureAwait(false):

static async Task<string> GetGooglePage(ProxyInfo proxy)
{
    using (var m=new MyNetworkClass())
    {
        m.Proxy = proxy;
        return await m.GetAsync("http://www.google.com").ConfigureAwait(false);
    }
}

This way, when the method resumes, it won't try resuming on the UI thread, so you won't get the deadlock. If you're using await in GetAsync() or the methods it calls, you will need to do the same thing there too.

In general, it's a good idea to use ConfigureAwait(false) everywhere, as long as you don't need to resume of the UI thread.

svick
  • 236,525
  • 50
  • 385
  • 514