在ASP.NET MVC中未调用WebClient异步回调

THX*_*138 5 asp.net-mvc multithreading

在GET请求我运行(类似):

public ActionResult Index(void) {
    webClient.DownloadStringComplete += onComplete;
    webClient.DownloadStringAsync(...);
    return null;
}
Run Code Online (Sandbox Code Playgroud)

我发现onCompleteIndex()完成执行之后才会调用它.我可以看到,onComplete在与Index执行的线程不同的线程上调用它.

问题:为什么会发生这种情况?为什么webClient的异步线程显然被阻塞,直到请求处理线程完成?

有没有办法解决这个问题,而无需启动新线程ThreadPool(我试过这个,并且使用线程池确实按预期工作.如果从ThreadPool的线程调用DownloadStringAsync,webClient的回调也会按预期发生).

ASP.NET MVC 3.0,.NET 4.0,MS Cassini开发Web服务器(VS 2010)

编辑:这是一个完整的代码:

public class HomeController : Controller {
    private static ManualResetEvent done;

    public ActionResult Index() {
        return Content(DownloadString() ? "success" : "failure");
    }

    private static bool DownloadString() {
        try {
            done = new ManualResetEvent(false);
            var wc = new WebClient();
            wc.DownloadStringCompleted += (sender, args) => { 
                // this breakpoint is not hit until after Index() returns.
                // It is weird though, because response isn't returned to the client (browser) until this callback finishes.
                // Note: This thread is different from one Index() was running on.
                done.Set(); 
            };

            var uri = new Uri(@"http://us.battle.net/wow/en/character/blackrock/hunt/simple");

            wc.DownloadStringAsync(uri);

            var timedout = !done.WaitOne(3000);
            if (timedout) {
                wc.CancelAsync();
                // if this would be .WaitOne() instead then deadlock occurs.
                var timedout2 = !done.WaitOne(3000); 
                Console.WriteLine(timedout2);
                return !timedout2;
            }
            return true;
        }
        catch (Exception ex) {
            Console.WriteLine(ex.Message);
        }
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

Ran*_*ngy 5

我很好奇,所以我询问了Microsoft内部的ASP.NET讨论别名,并得到了Levi Broderick的回复:

ASP.NET在内部使用SynchronizationContext进行同步,并且一次只允许一个线程控制该锁.在您的特定示例中,运行HomeController :: DownloadString的线程持有锁,但它正在等待触发ManualResetEvent.在DownloadStringCompleted方法运行之前,不会触发ManualResetEvent,但该方法在不能进行同步锁定的其他线程上运行,因为第一个线程仍然保留它.你现在陷入僵局.

我很惊讶这在MVC 2中曾经有效,但是如果它确实如此,那只是出乎意料的事故.这从未得到支持.