C#:方法Invoke从不返回
本文关键字:返回 Invoke 方法 | 更新日期: 2023-09-27 18:00:48
我有一个永远不会返回的线程调用。
线程一直运行得很好,直到我这样调用行"owner.Invoke(methInvoker);
">
调试时,我可以慢慢地一步一步,但一旦我碰到owner.Invoke
。。。结束了!
Control owner;
public event ReportCeProgressDelegate ProgressChanged;
public void ReportProgress(int step, object data) {
if ((owner != null) && (ProgressChanged != null)) {
if (!CancellationPending) {
ThreadEventArg e = new ThreadEventArg(step, data);
if (owner.InvokeRequired) {
MethodInvoker methInvoker = delegate { ProgressChanged(this, e); };
owner.Invoke(methInvoker);
} else {
ProgressChanged(this, e);
}
} else {
mreReporter.Set();
mreReporter.Close();
}
}
}
仅供参考:这是一个模仿BackgroundWorker
类的自定义类,该类在没有Forms的控件上不可用。
考虑到可能不需要Invoke,我手动将调试器中的光标移到代码的这一部分,并尝试直接调用ProgressChanged
,但VS2010的调试器引发了一个跨线程异常。
编辑:
由于我收到的前3条评论,我想用我的ProgressChanged
方法更新:
worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
if (progressBar1.Style != ProgressBarStyle.Continuous) {
progressBar1.Value = 0;
object data = e.Data;
if (data != null) {
progressBar1.Maximum = 100;
}
progressBar1.Style = ProgressBarStyle.Continuous;
}
progressBar1.Value = e.ProgressPercentage;
};
匿名方法的第一行有一个断点,但它也从未被命中。
编辑2
以下是对线程的调用的更完整列表:
List<TableData> tList = CollectTablesFromForm();
if (0 < tList.Count) {
using (SqlCeReporter worker = new SqlCeReporter(this)) {
for (int i = 0; i < tList.Count; i++) {
ManualResetEvent mre = new ManualResetEvent(false);
worker.StartThread += SqlCeClass.SaveSqlCeDataTable;
worker.ProgressChanged += delegate(object sender, ThreadEventArg e) {
if (progressBar1.Style != ProgressBarStyle.Continuous) {
progressBar1.Value = 0;
object data = e.Data;
if (data != null) {
progressBar1.Maximum = 100;
}
progressBar1.Style = ProgressBarStyle.Continuous;
}
progressBar1.Value = e.ProgressPercentage;
};
worker.ThreadCompleted += delegate(object sender, ThreadResultArg e) {
Cursor = Cursors.Default;
progressBar1.Visible = false;
progressBar1.Style = ProgressBarStyle.Blocks;
if (e.Error == null) {
if (e.Cancelled) {
MessageBox.Show(this, "Save Action was Cancelled.", "Save Table " + tList[i].TableName);
}
} else {
MessageBox.Show(this, e.Error.Message, "Error Saving Table " + tList[i].TableName, MessageBoxButtons.OK, MessageBoxIcon.Error);
}
mre.Set();
};
worker.RunWorkerAsync(tList[i]);
progressBar1.Value = 0;
progressBar1.Style = ProgressBarStyle.Marquee;
progressBar1.Visible = true;
Cursor = Cursors.WaitCursor;
mre.WaitOne();
}
}
}
我希望这不是小题大做!我讨厌提供太多信息,因为这样我就会受到人们对我风格的批评
worker.RunWorkerAsync(tList[i]);
//...
mre.WaitOne();
这肯定会陷入僵局。传递给Control.Begin/Invoke((的委托只能在UI线程空闲时运行,因为它已重新进入消息循环。您的UI线程没有空闲,它在WaitOne((调用中被阻止。在工作线程完成之前,该调用无法完成。在Invoke((调用完成之前,工作线程无法完成。在UI线程空闲之前,该调用无法完成。僵局城市。
阻塞UI线程从根本上说是错误的做法。不仅仅是因为.NET管道,COM已经要求它永远不要阻塞。这就是为什么BGW有一个RunWorkerCompleted事件。
很可能是UI和工作线程死锁了。Control.Invoke
通过将消息发布到UI线程的消息队列来将委托的执行封送到UI线程上,然后等待处理该消息,这反过来意味着必须在Control.Invoke
返回之前完成委托的执行。但是,如果您的UI线程除了调度和处理消息外,还忙于做其他事情,该怎么办?我可以从您的代码中看到,ManualResetEvent
可能在这里发挥作用。您的UI线程在调用WaitOne
时是否被意外阻止?如果是这样的话,那肯定是个问题。由于WaitOne
不会泵送消息,它将阻塞UI线程,当从工作线程调用Control.Invoke
时,这将导致死锁。
如果您希望ProgressChanged
事件的行为与BackgroundWorker
类似,则需要调用Control.Invoke
将这些事件处理程序放到UI线程上。无论如何,BackgroundWorker
就是这样工作的。当然,您不需要在这方面完全模仿BackgroundWorker
类,只要您准备好让调用方在处理ProgressChanged
事件时执行自己的封送处理即可。