C# BackgroundWorker OnWorkCompleted 抛出跨线程异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/818767/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
BackgroundWorker OnWorkCompleted throws cross-thread exception
提问by Noam Gal
I have a simple UserControl for database paging, that uses a controller to perform the actual DAL calls. I use a BackgroundWorker
to perform the heavy lifting, and on the OnWorkCompleted
event I re-enable some buttons, change a TextBox.Text
property and raise an event for the parent form.
我有一个用于数据库分页的简单 UserControl,它使用控制器来执行实际的 DAL 调用。我使用 aBackgroundWorker
来执行繁重的工作,并在OnWorkCompleted
事件中重新启用一些按钮、更改TextBox.Text
属性并为父窗体引发事件。
Form A holds my UserControl. When I click on some button that opens form B, even if I don't do anything "there" and just close it, and try to bring in the next page from my database, the OnWorkCompleted
gets called on the worker thread (and not my Main thread), and throws a cross-thread exception.
表单 A 包含我的用户控件。当我单击打开表单 B 的某个按钮时,即使我没有在“那里”做任何事情而只是关闭它,并尝试从我的数据库中引入下一页,也会OnWorkCompleted
在工作线程(而不是我的)上调用主线程),并抛出跨线程异常。
At the moment I added a check for InvokeRequired
at the handler there, but isn't the whole point of OnWorkCompleted
is to be called on the Main thread? Why wouldn't it work as expected?
目前我InvokeRequired
在那里的处理程序中添加了一个检查,但不是OnWorkCompleted
要在主线程上调用整个点吗?为什么它不能按预期工作?
EDIT:
编辑:
I have managed to narrow down the problem to arcgis and BackgroundWorker
. I have the following solution wich adds a Command to arcmap, that opens a simple Form1
with two buttons.
我已经设法将问题缩小到 arcgis 和BackgroundWorker
. 我有以下解决方案,它向 arcmap 添加了一个命令,它打开一个Form1
带有两个按钮的简单命令。
The first button runs a BackgroundWorker
that sleeps for 500ms and updates a counter.
In the RunWorkerCompleted
method it checks for InvokeRequired
, and updates the title to show whethever the method was originaly running inside the main thread or the worker thread.
The second button just opens Form2
, which contains nothing.
第一个按钮运行BackgroundWorker
睡眠 500 毫秒并更新计数器。在RunWorkerCompleted
方法中,它检查InvokeRequired
,并更新标题以显示该方法最初是在主线程还是工作线程中运行。第二个按钮刚刚打开Form2
,其中不包含任何内容。
At first, all the calls to RunWorkerCompletedare
are made inside the main thread (As expected - thats the whold point of the RunWorkerComplete method, At least by what I understand from the MSDNon BackgroundWorker
)
首先,所有调用RunWorkerCompletedare
都在主线程内进行(正如预期的那样 - 这就是 RunWorkerComplete 方法的重点,至少根据我从MSDN上的理解BackgroundWorker
)
After opening and closing Form2
, the RunWorkerCompleted
is always being called on the worker thread. I want to add that I can just leave this solution to the problem as is (check for InvokeRequired
in the RunWorkerCompleted
method), but I want to understand why it is happening against my expectations. In my "real" code I'd like to always know that the RunWorkerCompleted
method is being called on the main thread.
在打开和关闭之后Form2
,RunWorkerCompleted
总是在工作线程上调用。我想补充一点,我可以将这个解决方案保留原样(InvokeRequired
在RunWorkerCompleted
方法中检查),但我想了解为什么它会违背我的预期。在我的“真实”代码中,我想始终知道该RunWorkerCompleted
方法是在主线程上调用的。
I managed to pin point the problem at the form.Show();
command in my BackgroundTesterBtn
- if I use ShowDialog()
instead, I get no problem (RunWorkerCompleted
always runs on the main thread). I do need to use Show()
in my ArcMap project, so that the user will not be bound to the form.
我设法form.Show();
在我的命令中指出了问题BackgroundTesterBtn
- 如果我ShowDialog()
改为使用,我没有问题(RunWorkerCompleted
总是在主线程上运行)。我确实需要Show()
在我的 ArcMap 项目中使用,这样用户就不会被绑定到表单。
I also tried to reproduce the bug on a normal WinForms project. I added a simple project that just opens the first form without ArcMap, but in that case I couldn't reproduce the bug - the RunWorkerCompleted
ran on the main thread, whether I used Show()
or ShowDialog()
, before and after opening Form2
. I tried adding a third form to act as a main form before my Form1
, but it didn't change the outcome.
我还尝试在普通 WinForms 项目上重现该错误。我添加了一个简单的项目,它只在没有 ArcMap 的情况下打开第一个表单,但在这种情况下,我无法重现该错误 -RunWorkerCompleted
在主线程上运行,无论我使用Show()
还是ShowDialog()
,在打开Form2
. 我尝试在 my 之前添加第三种形式作为主要形式Form1
,但它没有改变结果。
Hereis my simple sln (VS2005sp1) - it requires
这是我的简单 sln (VS2005sp1) - 它需要
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ADF(9.2.4.1420)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.ArcMapUI(9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
ESRI.ArcGIS.SystemUI (9.2.3.1380)
采纳答案by Max Galkin
It looks like a bug:
它看起来像一个错误:
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=116930
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
http://thedatafarm.com/devlifeblog/archive/2005/12/21/39532.aspx
So I suggest using the bullet-proof (pseudocode):
所以我建议使用防弹(伪代码):
if(control.InvokeRequired)
control.Invoke(Action);
else
Action()
回答by Orion Edwards
Isn't the whole point of
OnWorkCompleted
is to be called on the Main thread? Why wouldn't it work as expected?
不是
OnWorkCompleted
要在主线程上调用整个点吗?为什么它不能按预期工作?
No, it's not.
You can't just go running any old thing on any old thread. Threads are not polite objects that you can simply say "run this, please".
不,这不对。
你不能只是在任何旧线程上运行任何旧东西。线程不是礼貌对象,您可以简单地说“请运行它”。
A better mental model of a thread is a freight train. Once it's going, it's off on it's own track. You can't change it's course or stop it. If you want to influence it, you either have to wait til it gets to the next train station (eg: have it manually check for some events), or derail it (Thread.Abort
and CrossThread exceptions have much the same consequences as derailing a train... beware!).
一个更好的线程心智模型是货运列车。一旦它走了,它就走上了自己的轨道。你不能改变它的进程或停止它。如果你想影响它,你要么必须等到它到达下一个火车站(例如:让它手动检查某些事件),要么让它脱轨(Thread.Abort
CrossThread 异常与火车脱轨的后果非常相似。 。 谨防!)。
Winforms controls sort ofsupport this behaviour (They have Control.BeginInvoke
which lets you run any function on the UI thread), but that only works because they have a special hook into the windows UI message pump and write some special handlers. To go with the above analogy, their train checks in at the station and looks for new directions periodically, and you can use that facility to post it your own directions.
WinForms控件类的支持,这种行为(他们有Control.BeginInvoke
它可以让你运行在UI线程上的任何功能),而只能是因为他们有一个特殊的钩子到Windows UI消息泵,并写了一些特殊的处理程序。按照上面的比喻,他们的火车在车站登记并定期寻找新的方向,您可以使用该设施发布您自己的方向。
The BackgroundWorker
is designed to be general purpose (it can't be tied to the windows GUI) so it can't use the windows Control.BeginInvoke
features. It has to assume that your main thread is an unstoppable 'train' doing it's own thing, so the completed event has to run in the worker thread or not at all.
它BackgroundWorker
被设计为通用的(它不能绑定到 Windows GUI)所以它不能使用 WindowsControl.BeginInvoke
功能。它必须假设你的主线程是一个不可阻挡的“火车”,做它自己的事情,所以完成的事件必须在工作线程中运行或根本不运行。
However, as you're using winforms, in your OnWorkCompleted
handler, you can get the Window to execute anothercallback using the BeginInvoke
functionality I mentioned above. Like this:
但是,当您使用 winforms 时,在您的OnWorkCompleted
处理程序中,您可以使用我上面提到的功能让 Window 执行另一个回调BeginInvoke
。像这样:
// Assume we're running in a windows forms button click so we have access to the
// form object in the "this" variable.
void OnButton_Click(object sender, EventArgs e )
var b = new BackgroundWorker();
b.DoWork += ... blah blah
// attach an anonymous function to the completed event.
// when this function fires in the worker thread, it will ask the form (this)
// to execute the WorkCompleteCallback on the UI thread.
// when the form has some spare time, it will run your function, and
// you can do all the stuff that you want
b.RunWorkerCompleted += (s, e) { this.BeginInvoke(WorkCompleteCallback); }
b.RunWorkerAsync(); // GO!
}
void WorkCompleteCallback()
{
Button.Enabled = false;
//other stuff that only works in the UI thread
}
Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.
在访问 Result 属性之前,您的 RunWorkerCompleted 事件处理程序应始终检查 Error 和 Canceled 属性。如果引发异常或取消操作,则访问 Result 属性会引发异常。
回答by tofi9
The BackgroundWorker
checks whether the delegate instance, points to a class which supports the interface ISynchronizeInvoke
. Your DAL layer probably does not implement that interface. Normally, you would use the BackgroundWorker
on a Form
, which does support that interface.
的BackgroundWorker
检查是否委托实例,指向支撑接口的类ISynchronizeInvoke
。您的 DAL 层可能没有实现该接口。通常,您会使用BackgroundWorker
on a Form
,它确实支持该接口。
In case you want to use the BackgroundWorker
from the DAL layer and want to update the UI from there, you have three options:
如果您想使用BackgroundWorker
来自 DAL 层的 并且想从那里更新 UI,您有三个选项:
- you'd stay calling the
Invoke
method - implement the interface
ISynchronizeInvoke
on the DAL class, and redirect the calls manually (it's only three methods and a property) - before invoking the
BackgroundWorker
(so, on the UI thread), to callSynchronizationContext.Current
and to save the content instance in an instance variable. TheSynchronizationContext
will then give you theSend
method, which will exactly do whatInvoke
does.
- 你会一直调用这个
Invoke
方法 ISynchronizeInvoke
在 DAL 类上实现接口,并手动重定向调用(只有三个方法和一个属性)- 在调用
BackgroundWorker
(因此,在 UI 线程上)之前,调用SynchronizationContext.Current
内容实例并将其保存在实例变量中。TheSynchronizationContext
will then give you theSend
method,它会做什么Invoke
。
回答by Igor Brejc
The best approach to avoid issues with cross-threading in GUI is to use SynchronizationContext.
避免 GUI 中跨线程问题的最佳方法是使用 SynchronizationContext。