开发者

C#: Method Invoke never returns

开发者 https://www.devze.com 2023-03-16 22:44 出处:网络
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);\"

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.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号