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 的回调也会按预期发生) 。
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;
}
}
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;
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我对此很好奇,所以我询问了 Microsoft 内部 ASP.NET 讨论别名,并得到了 Levi Broderick 的回复:
I was curious about this so I asked on the Microsoft internal ASP.NET discussion alias, and got this response from Levi Broderick:
这就是使用异步处理的要点。您的主线程开始调用,然后继续执行其他有用的操作。当调用完成时,它从 IO 完成线程池中选择一个线程,并在其上调用您注册的回调方法(在本例中是您的 onComplete 方法)。这样,您就不需要使用昂贵的线程来等待长时间运行的 Web 调用完成。
无论如何,您使用的方法遵循基于事件的异步模式。您可以在这里阅读更多相关信息:http://msdn.microsoft.com/en -us/library/wewwczdw.aspx
(编辑)注意:忽略此答案,因为它无助于回答已澄清的问题。将其留待其下进行的讨论。
This is the point of using asynchronous processing. Your main thread starts the call, then goes on to do other useful things. When the call is complete, it picks a thread from the IO completion thread pool and calls your registered callback method on it (in this case your onComplete method). That way you don't need to have an expensive thread waiting around for a long-running web call to complete.
Anyway, the methods you're using follow the Event-based Asynchronous Pattern. You can read more about it here: http://msdn.microsoft.com/en-us/library/wewwczdw.aspx
(edit) Note: Disregard this answer as it does not help answer the clarified question. Leaving it up for the discussion that happened under it.
除了所选答案之外,请参阅本文以获取有关 WebClient 捕获 SynchronizationContext 原因的更多详细信息。
http://msdn.microsoft.com/en-gb/magazine/gg598924.aspx
In addition to the chosen answer, see this article for further details on why the WebClient captures the SynchronizationContext.
http://msdn.microsoft.com/en-gb/magazine/gg598924.aspx