后台工作者:确保 ProgressChanged 方法在执行 RunWorkerCompleted 之前已经完成
Background Worker: Make sure that ProgressChanged method has finished before executing RunWorkerCompleted
假设我正在使用后台工作者并且我有以下方法:
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
方法之前已经完成。但事实并非如此,我不明白为什么?
我得到以下输出:
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
。我可以在 Debug / Windows / Threads.
中看到这个
如果我不调用 UpdateGUI
int bw_ProgressChanged
方法,则不会发生错误。但是随后用户在 table 和 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
.
之前已经完成
所以我猜问题出在 NI Measurement Studio 2015 的 ScatterGraph
和 BackgroundWorker
的组合上。但是我不明白为什么?
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
方法中设置断点,那么调用堆栈如下所示:
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
最大的缺陷是你下面的假设是错误的。
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?
不要沉迷于在心理上对逻辑流进行序列化。使用 WinForms/WPF,您将发生两个完全独立且异步的事件。您让 BGW 向 UI 发送请求(通过 worker.ReportProgress
)以执行进度更新。 UI 线程必须接收该请求并在 bw_ProgressChanged
事件 运行 时安排。
独立于 BGW(通过 myWork
)决定终止,可能是通过完全完成作业,或者因为抛出了未捕获的异常,或者可能是最终用户希望在某个时间取消工作给定实例。这然后向 UI 线程发送请求到 运行 bw_RunWorkerCompleted
方法。 UI 必须再次将它安排在它的许多待办事项列表中。
好吧,您有确凿的证据表明 RunWorkerCompleted 事件 运行 是 而 ProgressChanged 事件 运行 是。当然,这通常是不可能的,他们应该 运行 在同一个线程上。
无论如何,这有两种可能发生的方式。更明显的是,事件处理程序实际上并不在 UI 线程上 运行。这是相当常见的事故,尽管您倾向于从导致的 InvalidOperationException 中注意到。然而,该异常并不总是可靠地引发,它使用启发式方法。请注意,您的 UpdateGraph() 方法不太可能触发它,因为它似乎没有使用标准的 .NET 控件。
诊断此事故很容易,只需在事件处理程序上设置一个断点并使用 Debug > Windows > Threads debugging window 来验证它 运行s on the main线。使用Debug.Print显示Thread.CurrentThread.ManagedId的值可以帮助确保UI线程上的所有调用运行。您可以通过确保在主线程上执行 RunWorkerAsync() 调用来修复它。
然后是 重新进入错误 的老鼠陷阱,它发生在 ProgressChanged 执行某项操作获取 UI 调度程序 运行ning 时再次。往往与线程竞赛一样难以调试。可能发生的三种基本方式:
使用臭名昭著的 Application.DoEvents()
它邪恶的继妹,ShowDialog()。 ShowDialog 是伪装的 DoEvents,它通过禁用 UI 的 windows 假装不那么致命。这往往可以正常工作,除非您 运行 代码未被 UI 激活。就像这段代码。请注意,您似乎确实使用 MesssageBox.Show() 进行调试,这绝不是一个好主意。始终支持断点和 Debug.Print() 以避免此陷阱。
做一些阻塞 UI 线程的事情,比如锁、Thread.Join()、WaitOne()。阻塞 STA 线程在形式上是非法的,死锁的可能性很高,因此 CLR 对此采取了一些措施。它泵送自己的消息循环以确保避免死锁。是的,就像 DoEvents 所做的那样,它会进行一些过滤以避免讨厌的情况。但是对于这段代码来说还不够。请注意,这可能是由您未编写的代码完成的,例如 Graph 控件。
通过在 RunWorkerCompleted 事件上设置断点来诊断重入错误。您应该会看到 ProgressChanged 事件处理程序,它深埋在调用堆栈中。以及导致重入的语句。如果跟踪不能帮助您解决问题,那么 post 它在您的问题中。
假设我正在使用后台工作者并且我有以下方法:
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
方法之前已经完成。但事实并非如此,我不明白为什么?
我得到以下输出:
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
。我可以在 Debug / Windows / Threads.
如果我不调用 UpdateGUI
int bw_ProgressChanged
方法,则不会发生错误。但是随后用户在 table 和 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
.
所以我猜问题出在 NI Measurement Studio 2015 的 ScatterGraph
和 BackgroundWorker
的组合上。但是我不明白为什么?
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
方法中设置断点,那么调用堆栈如下所示:
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
最大的缺陷是你下面的假设是错误的。
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?
不要沉迷于在心理上对逻辑流进行序列化。使用 WinForms/WPF,您将发生两个完全独立且异步的事件。您让 BGW 向 UI 发送请求(通过 worker.ReportProgress
)以执行进度更新。 UI 线程必须接收该请求并在 bw_ProgressChanged
事件 运行 时安排。
独立于 BGW(通过 myWork
)决定终止,可能是通过完全完成作业,或者因为抛出了未捕获的异常,或者可能是最终用户希望在某个时间取消工作给定实例。这然后向 UI 线程发送请求到 运行 bw_RunWorkerCompleted
方法。 UI 必须再次将它安排在它的许多待办事项列表中。
好吧,您有确凿的证据表明 RunWorkerCompleted 事件 运行 是 而 ProgressChanged 事件 运行 是。当然,这通常是不可能的,他们应该 运行 在同一个线程上。
无论如何,这有两种可能发生的方式。更明显的是,事件处理程序实际上并不在 UI 线程上 运行。这是相当常见的事故,尽管您倾向于从导致的 InvalidOperationException 中注意到。然而,该异常并不总是可靠地引发,它使用启发式方法。请注意,您的 UpdateGraph() 方法不太可能触发它,因为它似乎没有使用标准的 .NET 控件。
诊断此事故很容易,只需在事件处理程序上设置一个断点并使用 Debug > Windows > Threads debugging window 来验证它 运行s on the main线。使用Debug.Print显示Thread.CurrentThread.ManagedId的值可以帮助确保UI线程上的所有调用运行。您可以通过确保在主线程上执行 RunWorkerAsync() 调用来修复它。
然后是 重新进入错误 的老鼠陷阱,它发生在 ProgressChanged 执行某项操作获取 UI 调度程序 运行ning 时再次。往往与线程竞赛一样难以调试。可能发生的三种基本方式:
使用臭名昭著的 Application.DoEvents()
它邪恶的继妹,ShowDialog()。 ShowDialog 是伪装的 DoEvents,它通过禁用 UI 的 windows 假装不那么致命。这往往可以正常工作,除非您 运行 代码未被 UI 激活。就像这段代码。请注意,您似乎确实使用 MesssageBox.Show() 进行调试,这绝不是一个好主意。始终支持断点和 Debug.Print() 以避免此陷阱。
做一些阻塞 UI 线程的事情,比如锁、Thread.Join()、WaitOne()。阻塞 STA 线程在形式上是非法的,死锁的可能性很高,因此 CLR 对此采取了一些措施。它泵送自己的消息循环以确保避免死锁。是的,就像 DoEvents 所做的那样,它会进行一些过滤以避免讨厌的情况。但是对于这段代码来说还不够。请注意,这可能是由您未编写的代码完成的,例如 Graph 控件。
通过在 RunWorkerCompleted 事件上设置断点来诊断重入错误。您应该会看到 ProgressChanged 事件处理程序,它深埋在调用堆栈中。以及导致重入的语句。如果跟踪不能帮助您解决问题,那么 post 它在您的问题中。