在ASP.NET MVC中未调用WebClient异步回调 [英] WebClient async callback not called in ASP.NET MVC

查看:83
本文介绍了在ASP.NET MVC中未调用WebClient异步回调的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

public ActionResult Index(void) {
    webClient.DownloadStringComplete += onComplete;
    webClient.DownloadStringAsync(...);
    return null;
}

我看到onComplete直到Index()完成执行后才被调用. 我看到onComplete是在与执行Index不同的线程上调用的.

问题:为什么会这样?为什么在请求处理线程完成之前,webClient的异步线程被明显阻塞了?

是否有一种方法可以解决此问题而无需从ThreadPool启动新线程(我尝试了这一点,并且使用线程池确实按预期工作.如果从ThreadPool的线程调用DownloadStringAsync,webClient的回调也按预期发生了)./p>

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;
    }
}

解决方案

我对此感到很好奇,因此我询问了Microsoft内部ASP.NET讨论别名,并从Levi Broderick得到了以下答复:

ASP.NET内部使用 的SynchronizationContext 同步,只有一个线程 一次允许 控制该锁.在你的 特定示例,线程正在运行 HomeController :: DownloadString保存 锁,但它正在等待 要触发的ManualResetEvent.这 在以下情况下,不会触发ManualResetEvent DownloadStringCompleted方法 运行,但是该方法在 永远无法占用的不同线程 同步锁,因为 第一个线程仍然保留它.你是 现在陷入僵局.

我很惊讶这曾经在 MVC 2,但如果这样做,只能通过 不幸的事故.从来没有 支持.

On GET request I run (something like):

public ActionResult Index(void) {
    webClient.DownloadStringComplete += onComplete;
    webClient.DownloadStringAsync(...);
    return null;
}

I see that onComplete isn't get invoked until after Index() has finished execution. I can see that onComplete is invoked on a different thread from one Index was executed on.

Question: why is this happening? why is webClient's async thread is apparently blocked until request handling thread is finished?

Is there a way to fix this without starting new thread from ThreadPool (I tried this, and using thread pool does work as expected. Also webClient's callback does happen as expected if DownloadStringAsync is called from a ThreadPool's thread).

ASP.NET MVC 3.0, .NET 4.0, MS Cassini dev web server (VS 2010)

EDIT: Here is a full code:

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;
    }
}

解决方案

I was curious about this so I asked on the Microsoft internal ASP.NET discussion alias, and got this response from Levi Broderick:

ASP.NET internally uses the SynchronizationContext for synchronization, and only one thread at a time is ever allowed to have control of that lock. In your particular example, the thread running HomeController::DownloadString holds the lock, but it’s waiting for the ManualResetEvent to be fired. The ManualResetEvent won’t be fired until the DownloadStringCompleted method runs, but that method runs on a different thread that can’t ever take the synchronization lock because the first thread still holds it. You’re now deadlocked.

I’m surprised that this ever worked in MVC 2, but if it did it was only by happy accident. This was never supported.

这篇关于在ASP.NET MVC中未调用WebClient异步回调的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆