TL;DR: 您的Web请求处理程序将在不同的线程上执行。确保您不要在处理程序中执行任何不安全的操作。您可以使用
Invoke
将回调处理程序的代码分派到主线程。
诊断:
问题几乎肯定隐藏在您的异步调用的缺少细节中。
// here I call a remote webservice asynchronously.
异步这个词有点含糊不清,很难确定具体发生了什么,但非常有可能您正在使用的异步机制已经在不同的线程上执行了回调函数,而不是主UI线程。
概述
在.NET模型中,这是很常见的。 .NET模型中的异步I/O利用线程池中的线程通过I/O完成端口(IOCP)处理I/O。这意味着当像Socket.BeginReceive
或WebRequest.BeginGetResponse
(或任何使用类似技术的.NET异步Web请求)这样的调用完成时,回调将在线程池中的线程上执行,而不是主线程。这可能会让您感到惊讶,因为您并没有积极地创建另一个线程;您只是参与了进行异步调用。
在网络请求的回调中,您必须非常小心,因为许多用户界面/Windows Forms操作只允许在主UI线程上执行。同样,可能不是UI本身引起了问题,而是您可能访问了一些不安全的资源或对象。许多看似无害的事情如果您在多线程上不小心操作可能会导致崩溃或异常。
解决方案:
如果您有疑问,在您的回调函数中,尽早地将代码分派(又名Invoke
)到处理程序中,以便在主线程上运行。
实现这个的常见模式如下所示。
假设您已经进行了如下调用:
IAsyncResult result = (IAsyncResult myHttpWebRequest.BeginGetResponse(
new AsyncCallback(RespoCallback), myRequestState);
处理程序可能设置如下所示:
private static void RespCallback(IAsyncResult asynchronousResult)
{
// THIS IS NOT GOING TO WORK BECAUSE WE ARE ON THE WRONG THREAD. e.g.:
this.label1.Text = "OK"; // BOOM! :(
}
相反,将任何必要的处理任务分派回主线程。
private static void RespCallback(IAsyncResult asynchronousResult)
{
this.Invoke((MethodInvoker) delegate {
// This block of code will run on the main thread.
// It is safe to do UI things now. e.g.:
this.label1.Text = "OK"; // HOORAY! :)
});
}
我不建议将这作为一般最佳实践。我不是说要立即将所有处理程序调度回主线程。并非一刀切。您应该仔细查看处理程序中的具体细节,并确保您不会执行特定于线程的操作。但是,如果您没有对异步处理程序正在执行的内容进行任何解释,那么问题很可能通过在主线程上调用处理程序代码来解决。
注意:当然,要使用此技术解决您的问题,需要确保您的主线程正在运行。如果您使用(糟糕的)技术阻塞了主线程,例如此示例中的技术,则必须重新设计应用程序的某些部分。以下是需要更大改造的示例:
IAsyncResult result=
(IAsyncResult) myHttpWebRequest.BeginGetResponse(new AsyncCallback(RespCallback),myRequestState);
ThreadPool.RegisterWaitForSingleObject (result.AsyncWaitHandle, new WaitOrTimerCallback(TimeoutCallback), myHttpWebRequest, DefaultTimeout, true);
allDone.WaitOne();
注意
WaitOne
的调用吗?这会阻塞执行线程。如果此代码在主线程上执行,则主线程将被阻塞,直到WebRequest完成。您需要重新设计,以便不阻塞主线程(我的建议),或更仔细地检查回调处理程序,以查看其所做的工作与其他线程冲突的原因。