3
public class HomeController : Controller
{
    public ActionResult Index()
    {
        Task<string> t = DownloadStringTaskAsync(new Uri("http://www.google.com/search?q=aspnet"));
        t.Wait();
        string res = t.Result;

        return View((object)res);
    }

    Task<string> DownloadStringTaskAsync(Uri address)
    {
        TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
        WebClient wc = new WebClient();
        wc.DownloadStringCompleted += (s, e) => tcs.SetResult(e.Result);
        wc.DownloadStringAsync(address);
        return tcs.Task;
    }
}

The action will never return and the request will simply timeout.

We are using TPL tasks for all asynchronous operations and had to wrap some of WebClient's methods until we switch to framework 4.5.

The above method DownloadStringTaskAsync is a simplified version of one of those wrappers.

What I can't figure out is why waiting on the task deadlocks the thread in aspnet and works fine in console or desktop applications.

Here's how the deadlocked threads look like.

request thread

[In a sleep, wait, or join]
mscorlib.dll!System.Threading.Monitor.Wait(object obj, int millisecondsTimeout, bool exitContext) + 0x18 bytes mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) + 0x264 bytes
mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) + 0x166 bytes mscorlib.dll!System.Threading.Tasks.Task.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) + 0x41 bytes
mscorlib.dll!System.Threading.Tasks.Task.Wait() + 0xb bytes
TCSProblem.DLL!TCSProblem.Controllers.HomeController.Index() Line 16 + 0xa bytes

WebClient thread

[In a sleep, wait, or join]
mscorlib.dll!System.Threading.Monitor.Enter(object obj, ref bool lockTaken) + 0x14 bytes
System.Web.dll!System.Web.AspNetSynchronizationContext.CallCallback(System.Threading.SendOrPostCallback callback, object state) + 0x53 bytes
System.Web.dll!System.Web.AspNetSynchronizationContext.Post(System.Threading.SendOrPostCallback callback, object state) + 0x9 bytes
System.dll!System.ComponentModel.AsyncOperation.Post(System.Threading.SendOrPostCallback d, object arg) + 0x29 bytes
System.dll!System.Net.WebClient.PostProgressChanged(System.ComponentModel.AsyncOperation asyncOp, System.Net.WebClient.ProgressData progress) + 0x25c bytes

Community
  • 1
  • 1
Angel Yordanov
  • 3,112
  • 1
  • 22
  • 19
  • Your usage of async downloading does not help, but hurt. I guess this code is taken from a larger code sample? – usr Jun 28 '12 at 12:20
  • FWIW, as written you could remove the 't.Wait();' since accessing the Result property will block until the Task has completed. – James Manning Jun 28 '12 at 13:30

2 Answers2

11

WebClient, unfortunately, captures the current synchronization context and raises the completion event by posting to it. Because of that the completion action only runs after you return from your action method which will never happen. This means, you either...

  1. ...need to wrap your call to DownloadStringAsync in a Task.Factory.StartNew in order to switch to a thread that has no synccontext
  2. ...need to set SynchronizationContext.Current to null before calling, and restore it afterwards
  3. ...use an asynchronous MVC action
usr
  • 168,620
  • 35
  • 240
  • 369
-1

All .NET classes captures the SynchronizationContext by default, I had a similar problem: SmtpClient.SendAsync blocking my ASP.NET MVC Request

Community
  • 1
  • 1
Felipe Pessoto
  • 6,855
  • 10
  • 42
  • 73