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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 02:24:26  来源:igfitidea点击:

BackgroundWorker OnWorkCompleted throws cross-thread exception

c#winformsbackgroundworkerarcgismultithreading

提问by Noam Gal

I have a simple UserControl for database paging, that uses a controller to perform the actual DAL calls. I use a BackgroundWorkerto perform the heavy lifting, and on the OnWorkCompletedevent I re-enable some buttons, change a TextBox.Textproperty 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 OnWorkCompletedgets 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 InvokeRequiredat the handler there, but isn't the whole point of OnWorkCompletedis 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 Form1with two buttons.

我已经设法将问题缩小到 arcgis 和BackgroundWorker. 我有以下解决方案,它向 arcmap 添加了一个命令,它打开一个Form1带有两个按钮的简单命令。

The first button runs a BackgroundWorkerthat sleeps for 500ms and updates a counter. In the RunWorkerCompletedmethod 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 RunWorkerCompletedareare 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 RunWorkerCompletedis 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 InvokeRequiredin the RunWorkerCompletedmethod), but I want to understand why it is happening against my expectations. In my "real" code I'd like to always know that the RunWorkerCompletedmethod is being called on the main thread.

在打开和关闭之后Form2RunWorkerCompleted总是在工作线程上调用。我想补充一点,我可以将这个解决方案保留原样(InvokeRequiredRunWorkerCompleted方法中检查),但我想了解为什么它会违背我的预期。在我的“真实”代码中,我想始终知道该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 (RunWorkerCompletedalways 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 RunWorkerCompletedran 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 OnWorkCompletedis 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.Abortand CrossThread exceptions have much the same consequences as derailing a train... beware!).

一个更好的线程心智模型是货运列车。一旦它走了,它就走上了自己的轨道。你不能改变它的进程或停止它。如果你想影响它,你要么必须等到它到达下一个火车站(例如:让它手动检查某些事件),要么让它脱轨(Thread.AbortCrossThread 异常与火车脱轨的后果非常相似。 。 谨防!)。

Winforms controls sort ofsupport this behaviour (They have Control.BeginInvokewhich 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 BackgroundWorkeris designed to be general purpose (it can't be tied to the windows GUI) so it can't use the windows Control.BeginInvokefeatures. 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 OnWorkCompletedhandler, you can get the Window to execute anothercallback using the BeginInvokefunctionality 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
}

Also, don't forget this:

另外,不要忘记这一点:

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 BackgroundWorkerchecks 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 BackgroundWorkeron a Form, which does support that interface.

BackgroundWorker检查是否委托实例,指向支撑接口的类ISynchronizeInvoke。您的 DAL 层可能没有实现该接口。通常,您会使用BackgroundWorkeron a Form,它确实支持该接口。

In case you want to use the BackgroundWorkerfrom the DAL layer and want to update the UI from there, you have three options:

如果您想使用BackgroundWorker来自 DAL 层的 并且想从那里更新 UI,您有三个选项:

  • you'd stay calling the Invokemethod
  • implement the interface ISynchronizeInvokeon 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 call SynchronizationContext.Currentand to save the content instance in an instance variable. The SynchronizationContextwill then give you the Sendmethod, which will exactly do what Invokedoes.
  • 你会一直调用这个Invoke方法
  • ISynchronizeInvoke在 DAL 类上实现接口,并手动重定向调用(只有三个方法和一个属性)
  • 在调用BackgroundWorker(因此,在 UI 线程上)之前,调用SynchronizationContext.Current内容实例并将其保存在实例变量中。The SynchronizationContextwill then give you the Sendmethod,它会做什么Invoke

回答by Igor Brejc

The best approach to avoid issues with cross-threading in GUI is to use SynchronizationContext.

避免 GUI 中跨线程问题的最佳方法是使用 SynchronizationContext