C#: Method Invoke never returns
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开发者_C百科.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. :)
worker.RunWorkerAsync(tList[i]);
//...
mre.WaitOne();
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.
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 before Control.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 a ManualResetEvent
may be in play here. Is your UI thread blocked on a call to WaitOne
by chance? If so that could definitely be the problem. Since WaitOne
does not pump messages it will block the UI thread which will lead to a deadlock when Control.Invoke
is called from your worker thread.
If you want your ProgressChanged
event to behave like it does with BackgroundWorker
then you will need to call Control.Invoke
to get those event handlers onto the UI thread. That is the way BackgroundWorker
works anyway. Of course, you do not have to mimic the BackgroundWorker
class exactly in that respect as long as you are prepared to have the callers do their own marshaling when handling the ProgressChanged
event.
精彩评论