后台工作者:在执行RunWorkerCompleted之前,请确保ProgressChanged方法已完成. [英] Background Worker: Make sure that ProgressChanged method has finished before executing RunWorkerCompleted

查看:83
本文介绍了后台工作者:在执行RunWorkerCompleted之前,请确保ProgressChanged方法已完成.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我使用的是背景工人,并且具有以下方法:

Let's assume I'm using a Background Worker and I've the following methods:

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    finalData = MyWork(sender as BackgroundWorker, e);
}

private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    int i = e.ProgressPercentage; // Missused for i
    Debug.Print("BW Progress Changed Begin, i: " + i + ", ThreadId: " + Thread.CurrentThread.ManagedThreadId);
    // I use this to update a table and an XY-Plot, so that the user can see the progess.
    UpdateGUI(e.UserState as MyData);
    Debug.Print("BW Progress Changed End,   i: " + i + ", ThreadId: " + Thread.CurrentThread.ManagedThreadId);
}

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if ((e.Cancelled == true))
    {
        // Cancelled
    }
    else if (!(e.Error == null))
    {
        MessageBox.Show(e.Error.Message);
    }
    else
    {        
        Debug.Print("BW Run Worker Completed Begin, ThreadId: " + Thread.CurrentThread.ManagedThreadId);
        // I use this to update a table and an XY-Plot, 
        // so that the user can see the final data.
        UpdateGUI(finalData);
        Debug.Print("BW Run Worker Completed End,   ThreadId: " + Thread.CurrentThread.ManagedThreadId);
    }
}

现在,我假设bw_ProgressChanged方法已经完成,然后调用了bw_RunWorkerCompleted方法.但这不是事实,我不明白为什么?

Now I would assume that the bw_ProgressChanged method has finished before the bw_RunWorkerCompleted method is called. But that's not the case and I don't understand why?

我得到以下输出:

Worker, i: 0, ThreadId: 27
BW Progress Changed Begin, i: 0, ThreadId: 8
BW Progress Changed End,   i: 0, ThreadId: 8
Worker, i: 1, ThreadId: 27
BW Progress Changed Begin, i: 1, ThreadId: 8
BW Progress Changed End,   i: 1, ThreadId: 8
Worker, i: 2, ThreadId: 27
BW Progress Changed Begin, i: 2, ThreadId: 8
BW Run Worker Completed Begin, ThreadId: 8
BW Run Worker Completed End,   ThreadId: 8
A first chance exception of type 'System.InvalidOperationException' occurred in mscorlib.dll
ERROR <-- Collection was modified; enumeration operation may not execute.
ERROR <-- NationalInstruments.UI.WindowsForms.Graph.ClearData()

MagagedID 8是Main Thread,而27是Worker Thread.我可以在调试"/"Windows"/线程"中看到它.

The MagagedID 8 is the Main Thread and 27 is a Worker Thread. I can see this in the Debug / Windows / Threads.

如果我不调用UpdateGUI int bw_ProgressChanged方法,则不会发生错误.但是随后用户在表格和XY绘图中看不到任何进度.

If I don't call UpdateGUI int the bw_ProgressChanged method then no error occurs. But then the user doesn't see any progress in the table and the XY-Plot.

编辑

MyWork方法如下所示:

public MyData[] MyWork(BackgroundWorker worker, DoWorkEventArgs e)
{
     MyData[] d = new MyData[n];
     for (int i = 0; i < n; i++) 
         d[i] = null;
     for (int i = 0; i < n; i++)
     {
         if (worker.CancellationPending == true)
         {
             e.Cancel = true;
             break;
         }
         else
         {
             d[i] = MyCollectDataPoint(); // takes about 1 to 10 seconds
             Debug.Print("Worker, i: " + i + ", ThreadId: " + Thread.CurrentThread.ManagedThreadId)
             worker.ReportProgress(i, d);
         }
     }
     return d;
}

UpdateGUI方法如下:

private void UpdateGUI(MyData d)
{
   UpdateTable(d); // updates a DataGridView
   UpdateGraph(d); // updates a ScatterGraph (NI Measurement Studio 2015)
}

如果我不调用UpdateGraph方法,它将按方面进行工作.因此,ProgressChanged方法在执行RunWorkerCompleted之前已经完成.

If I don't call UpdateGraph method it works as aspected. So the ProgressChanged method has finished before executing RunWorkerCompleted.

所以我想问题是NI Measurement Studio 2015中的ScatterGraphBackgroundWorker的结合.但是我不明白为什么?

So I guess the problem is the combination of the ScatterGraph from NI Measurement Studio 2015 and the BackgroundWorker. But I don't understand why?

UpdateGraph方法如下所示:

private void UpdateGraph(MyData d)
{
    plot.ClearData();
    plot.Plots.Clear(); // The error happens here (Collection was modified; enumeration operation may not execute).
    int n = MyGetNFromData(d);        
    for (int i = 0; i < n; i++)
    {
        ScatterPlot s = new ScatterPlot();
        double[] xi = MyGetXiFromData(d, i);
        double[] yi = MyGetYiFromData(d, i);
        s.XAxis = plot.XAxes[0];
        s.YAxis = plot.YAxes[0];
        s.LineWidth = 2;
        s.LineColor = Colors[i % Colors.Length];
        s.ProcessSpecialValues = true;
        s.PlotXY(xi, yi);
        plot.Plots.Add(s);
    }
}

编辑2

如果我在bw_RunWorkerCompleted方法中设置了一个断点,则调用堆栈如下所示:

If I set a breakpoint in the bw_RunWorkerCompleted method then the call stack looks like that:

bw_RunWorkerCompleted
[External Code]
UpdateGraph // Line: plot.ClearData()
UpdateGUI
bw_ProgressChanged
[External Code]
Program.Main

和第一个[External Code]块:

System.dll!System.ComponentModel.BackgroundWorker.OnRunWorkerCompleted(System.ComponentModel.RunWorkerCompletedEventArgs e) Unknown
[Native to Managed Transition]  
mscorlib.dll!System.Delegate.DynamicInvokeImpl(object[] args)   Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackDo(System.Windows.Forms.Control.ThreadMethodEntry tme) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(object obj) Unknown
mscorlib.dll!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state, bool preserveSyncCtx)   Unknown
mscorlib.dll!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallback(System.Windows.Forms.Control.ThreadMethodEntry tme)   Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.InvokeMarshaledCallbacks()    Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.MarshaledInvoke(System.Windows.Forms.Control caller, System.Delegate method, object[] args, bool synchronous) Unknown
System.Windows.Forms.dll!System.Windows.Forms.Control.Invoke(System.Delegate method, object[] args) Unknown
System.Windows.Forms.dll!System.Windows.Forms.WindowsFormsSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state)  Unknown
NationalInstruments.Common.dll!NationalInstruments.Restricted.CallbackManager.CallbackDispatcher.SynchronousCallbackDispatcher.InvokeWithContext(System.Delegate handler, object sender, System.EventArgs e, System.Threading.SynchronizationContext context, object state) Unknown
NationalInstruments.Common.dll!NationalInstruments.Restricted.CallbackManager.a(NationalInstruments.Restricted.CallbackManager.CallbackDispatcher A_0, object A_1, object A_2, System.EventArgs A_3)    Unknown
NationalInstruments.Common.dll!NationalInstruments.Restricted.CallbackManager.RaiseEvent(object eventKey, object sender, System.EventArgs e)    Unknown
NationalInstruments.Common.dll!NationalInstruments.ComponentBase.RaiseEvent(object eventKey, System.EventArgs e)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.XYCursor.OnAfterMove(NationalInstruments.UI.AfterMoveXYCursorEventArgs e) Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.XYCursor.a(object A_0, NationalInstruments.Restricted.ControlElementCursorMoveEventArgs A_1)  Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.OnAfterMove(NationalInstruments.Restricted.ControlElementCursorMoveEventArgs e)  Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.a(NationalInstruments.UI.Internal.CartesianPlotElement A_0, double A_1, double A_2, int A_3, bool A_4)   Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.MoveCursorFreely(double xValue, double yValue, bool isInteractive, NationalInstruments.UI.Internal.XYCursorElement.Movement movement)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.MoveCursorXY(double xValue, double yValue, bool isInteractive)   Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.ResetCursor()    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYCursorElement.a(object A_0, NationalInstruments.Restricted.ControlElementEventArgs A_1)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.PlotElement.OnDataChanged(NationalInstruments.Restricted.ControlElementEventArgs e)  Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.PlotElement.OnDataChanged()  Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.CartesianPlotElement.a(object A_0, NationalInstruments.UI.Internal.PlotDataChangedEventArgs A_1) Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYDataManager.a(NationalInstruments.UI.Internal.PlotDataChangedEventArgs A_0)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYDataManager.a(NationalInstruments.UI.Internal.PlotDataChangeCause A_0, int A_1)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.XYDataManager.ClearData(bool raiseDataChanged)   Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.CartesianPlotElement.ClearData(bool raiseDataChanged)    Unknown
NationalInstruments.UI.dll!NationalInstruments.UI.Internal.PlotElement.ClearData()  Unknown
NationalInstruments.UI.dll!NationalInstruments.Restricted.XYGraphManager.ClearData()    Unknown
NationalInstruments.UI.WindowsForms.dll!NationalInstruments.UI.WindowsForms.Graph.ClearData()   Unknown

推荐答案

好吧,您有确凿的证据表明RunWorkerCompleted事件运行事件运行.当然,通常这是不可能的,它们应该在同一线程上运行.

Well, you have hard evidence that the RunWorkerCompleted event runs while the ProgressChanged event runs. That is not normally possible of course, they are supposed to run on the same thread.

无论如何,有两种可能的方式.更为明显的是事件处理程序实际上并未在UI线程上运行.尽管您可能会从InvalidOperationException引起的注意中注意到这,这是相当普遍的事故.但是,该异常并非总是可靠地引发,它使用启发式方法.请注意,由于UpdateGraph()方法似乎未使用标准的.NET控件,因此不太可能使其跳闸.

There are two possible ways that this can happen anyway. The more obvious one is that the event handlers don't actually run on the UI thread. Which is fairly common mishap, although you tend to notice from the InvalidOperationException that causes. That exception is however not always reliably raised, it uses a heuristic. Beware that your UpdateGraph() method is not so likely to trip it since it doesn't appear to use a standard .NET control.

否则,诊断此事故很容易,只需在事件处理程序上设置一个断点,然后使用调试">"Windows">线程"调试窗口来验证它是否在主线程上运行.使用Debug.Print显示Thread.CurrentThread.ManagedId的值可以帮助确保所有调用都在UI线程上运行.通过确保在主线程上执行RunWorkerAsync()调用来对其进行修复.

Diagnosing this mishap is otherwise easy, just set a breakpoint on the event handler and use the Debug > Windows > Threads debugging window to verify it runs on the main thread. Using Debug.Print to display the value of Thread.CurrentThread.ManagedId can help ensure that all invocations run on the UI thread. You fix it by ensuring that the RunWorkerAsync() call is executed on the main thread.

然后是重新进入错误的陷阱,它发生在ProgressChanged做一些使UI调度程序重新运行的操作时.趋于像线程竞赛一样难以调试.可能发生的三种基本方式:

And then there is the rat trap of a re-entrancy bug, it occurs when ProgressChanged does something that gets the UI dispatcher running again. Tends to be about as hard to debug as a threading race. Three basic ways that can happen:

  • 使用臭名昭著的Application.DoEvents()

  • using the infamous Application.DoEvents()

其邪恶的姊妹ShowDialog(). ShowDialog是DoEvents的变相,它通过禁用UI窗口来降低致命性.除非您运行未由UI激活的代码,否则这似乎可以正常工作.像这样的代码.注意,您似乎确实使用MessageageBox.Show()进行调试,但这绝不是一个好主意.始终偏爱断点和Debug.Print()来避免此陷阱.

its evil step-sister, ShowDialog(). ShowDialog is DoEvents in disguise, it pretends to be less lethal by disabling the windows of the UI. Which tends to work okay, except when you run code that isn't activated by the UI. Like this code. Beware that you do appear to use MesssageBox.Show() for debugging, never a good idea. Always favor breakpoints and Debug.Print() to avoid this trap.

做一些阻止UI线程的操作,例如lock,Thread.Join(),WaitOne().正式禁止STA线程是非法的,死锁几率很高,因此CLR会为此做一些事情.它抽出自己的消息循环以确保避免死锁.是的,就像DoEvents一样,它会进行一些过滤以避免讨厌的情况.但是对于此代码而言,还不够.请注意,这可能是由您未编写的代码(例如Graph控件)完成的.

doing something that blocks the UI thread, like lock, Thread.Join(), WaitOne(). Blocking an STA thread is formally illegal, high odds for deadlock, so the CLR does something about it. It pumps its own message loop to ensure deadlock is avoided. Yes, like DoEvents does, it does some filtering to avoid the nasty cases. But not otherwise enough for this code. Beware that this might be done by code you did not write, like that Graph control.

通过在RunWorkerCompleted事件上设置断点来诊断重新进入错误.您应该看到将ProgressChanged事件处理程序放回调用栈的深处.以及引起重新进入的语句.如果跟踪无法解决问题,则将其发布到您的问题中.

Diagnose a re-entrancy bug by setting a breakpoint on the RunWorkerCompleted event. You should see the ProgressChanged event handler back, buried deep in the call stack. And the statement that causes the re-entrancy. If the trace doesn't help you figure it out then post it in your question.

这篇关于后台工作者:在执行RunWorkerCompleted之前,请确保ProgressChanged方法已完成.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆