2011-04-19 58 views
5

不叫WebClient的異步回調在GET請求,我跑(類似):在ASP.NET MVC

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

我看到onComplete不會被調用,直到後Index()執行結束。 我可以看到onComplete在一個不同的線程上被調用,其中Index被執行。

問題:爲什麼會發生這種情況?爲什麼在請求處理線程完成之前,webClient的異步線程顯然被阻塞?

有沒有一種方法可以解決這個問題,而不需要從ThreadPool開始新線程(我嘗試過這樣做,並且使用線程池可以按預期方式工作,如果從ThreadPool線程調用DownloadStringAsync,webClient的回調也會按預期發生。

ASP.NET MVC 3.0,.NET 4.0,MS卡西尼開發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; 
    } 
} 

回答

5

我很好奇這個,所以我問在微軟內部ASP.NET討論別名,並得到了列維·布羅德里克這樣的響應:

ASP.NET在內部使用 的SynchronizationContext用於 同步,只有一次有一個線程 曾被允許擁有該鎖的控制權 。在您的 特定示例中,正在運行的線程 HomeController :: DownloadString保存着 的鎖,但它正在等待ManualResetEvent被觸發 。該 的ManualResetEvent不會被解僱,直到 的DownloadStringCompleted方法 運行,但這種方法對永遠不能採取 同步鎖,因爲 第一個線程仍持有它 不同的線程中運行。你現在處於死鎖狀態, 。

我很驚訝,這曾經在 MVC 2工作,但如果它只是通過 幸福的事故。這從來沒有支持 。

1

這是使用點異步處理。你的主線程開始調用,然後繼續做其他有用的事情。調用完成後,它會從IO完成線程池中選取一個線程,並調用您的註冊回調方法(在本例中爲onComplete方法)。這樣你就不需要花費昂貴的線程來等待長時間運行的Web電話來完成。

無論如何,您使用的方法遵循基於事件的異步模式。你可以在這裏閱讀更多關於它的信息:http://msdn.microsoft.com/en-us/library/wewwczdw.aspx

(編輯)注意:無視這個答案,因爲它無助於回答澄清的問題。留下來討論它下面發生的討論。

+0

這說明了這一點。那麼,WebClient不使用ThreadPool? WebClient如何排隊請求?我用反射器將它反射了一段時間,但找不到它發生的地方。 – 2011-04-19 15:51:01

+0

它不排隊請求。它實際上是在那時開始請求。但是在啓動請求後,DownloadStringAsync()將返回並讓您在下載過程中執行其他操作。在下載過程中,實際上根本沒有線程!它只會從線程池中拉出一個線程來通知您完成。 – RandomEngy 2011-04-19 15:54:56

+0

我明白了。那麼在代碼中的回調被調用?我試圖在從Index返回之前放置Thread.Sleep(10000),但直到索引返回之前,回調仍然不會被調用。這意味着回調呼叫在索引調用後排隊。我很好奇這是怎麼發生的。如果在CLR ThreadPool上調用回調函數,它不應該被GET請求處理線程阻塞,是嗎? – 2011-04-19 15:59:24