C#:方法调用永远不会返回
我有一个永远不会返回的线程调用。
线程运行得很好,直到我调用该行,“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
类的自定义类,该类在没有表单的控件上不可用。
考虑到可能不需要 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();
}
}
}
我希望这不是矫枉过正!我讨厌提供太多信息,因为那样就会让人们批评我的风格。 :)
I've got a threaded invoke call that never returns.
The thread runs just fine right up until I call the line that ways, "owner.Invoke(methInvoker);
"
When debugging, I can slowly step, step, step, but once I hit owner.Invoke
... it's Over!
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();
}
}
}
FYI: This is a custom class that mimics the BackgroundWorker
class, which is not available on controls that do not have Forms.
Thinking Invoke might not be required, I manually stepped the cursor in the debugger over that part of the code and tried calling ProgressChanged
directly, but VS2010's debugger threw a cross thread exception.
EDIT:
Due to the first 3 comments I have received, I wanted to update with my ProgressChanged
method:
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;
};
There is a breakpoint on the first line of the anonymous method, but it never gets hit either.
EDIT 2
Here is a more complete listing of the call to the thread:
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();
}
}
}
I hope this isn't overkill! I hate presenting too much information, because then I get people critiquing my style. :)
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这肯定会陷入僵局。您传递给 Control.Begin/Invoke() 的委托只能在 UI 线程空闲且重新进入消息循环时运行。您的 UI 线程没有空闲,它在 WaitOne() 调用上被阻塞。在工作线程完成之前,该调用无法完成。在 Invoke() 调用完成之前,您的工作线程无法完成。在 UI 线程空闲之前,该调用无法完成。僵城。
阻塞 UI 线程从根本上来说是错误的做法。不仅仅是因为 .NET 管道,COM 已经要求它永远不会阻塞。这就是 BGW 有 RunWorkerCompleted 事件的原因。
That's a guaranteed deadlock. The delegate you pass to Control.Begin/Invoke() can only run when the UI thread is idle, having re-entered the message loop. Your UI thread isn't idle, it is blocked on the WaitOne() call. That call can't complete until your worker thread completes. Your worker thread can't complete until the Invoke() call is completed. That call can't complete until the UI thread goes idle. Deadlock city.
Blocking the UI thread is fundamentally a wrong thing to do. Not just because of .NET plumbing, COM already requires it to never block. That's why BGW has a RunWorkerCompleted event.
您可能已使 UI 和工作线程陷入僵局。 Control.Invoke 通过将消息发布到 UI 线程的消息队列来将委托的执行编组到 UI 线程上,然后等待该消息被处理,这反过来意味着委托的执行必须在
Control.Invoke
返回之前完成。但是,如果您的 UI 线程除了调度和处理消息之外还忙着做其他事情怎么办?我可以从您的代码中看到ManualResetEvent
可能正在此处发挥作用。您的 UI 线程是否偶然因调用WaitOne
而被阻塞?如果是这样,那肯定是问题所在。由于WaitOne
不会泵送消息,因此它会阻塞 UI 线程,从而在从工作线程调用Control.Invoke
时导致死锁。如果您希望
ProgressChanged
事件的行为与BackgroundWorker
的行为类似,那么您需要调用Control.Invoke
将这些事件处理程序添加到用户界面线程。这就是BackgroundWorker
的工作方式。当然,您不必在这方面完全模仿BackgroundWorker
类,只要您准备好让调用者在处理时进行自己的封送处理即可。 ProgressChanged 事件。
It is likely that you have deadlocked the UI and worker threads.
Control.Invoke
marshals the execution of a delegate onto the UI thread by posting a message to the UI thread's message queue and then waits for that message to be processed which in turn means the execution of the delegate has to complete beforeControl.Invoke
returns. But, what if your UI thread is busy doing something else beside dispatching and processing messages? I can see from your code that aManualResetEvent
may be in play here. Is your UI thread blocked on a call toWaitOne
by chance? If so that could definitely be the problem. SinceWaitOne
does not pump messages it will block the UI thread which will lead to a deadlock whenControl.Invoke
is called from your worker thread.If you want your
ProgressChanged
event to behave like it does withBackgroundWorker
then you will need to callControl.Invoke
to get those event handlers onto the UI thread. That is the wayBackgroundWorker
works anyway. Of course, you do not have to mimic theBackgroundWorker
class exactly in that respect as long as you are prepared to have the callers do their own marshaling when handling theProgressChanged
event.