C#:方法调用永远不会返回

时间:2011-07-01 15:23:26

标签: c# multithreading backgroundworker invoke

我有一个永不返回的线程调用调用。

线程运行正常,直到我用“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类的自定义类,在没有表单的控件上不可用。

可能不需要Thinking Invoke,我手动将调色器中的光标放在代码的那一部分上并尝试直接调用ProgressChanged,但是VS2010的调试器抛出了一个跨线程异常。

修改

由于我收到的前三条评论,我想用我的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();
    }
  }
}

我希望这不是矫枉过正!我讨厌提供太多的信息,因为那时我会让人批评我的风格。 :)

2 个答案:

答案 0 :(得分:4)

  worker.RunWorkerAsync(tList[i]);
  //...
  mre.WaitOne();

这是一个保证死锁。传递给Control.Begin / Invoke()的委托只能在UI线程空闲时重新进入消息循环。您的UI线程不是空闲的,它在WaitOne()调用时被阻止。在您的工作线程完成之前,该调用无法完成。在Invoke()调用完成之前,您的工作线程无法完成。在UI线程空闲之前,该调用无法完成。死锁城市。

阻止UI线程从根本上说是错误的。不仅仅是因为.NET管道,COM已经要求它永远不会阻塞。这就是为什么BGW有一个RunWorkerCompleted事件。

答案 1 :(得分:1)

您可能已经使UI和工作线程死锁。 Control.Invoke通过向UI线程的消息队列发送消息,然后等待处理该消息,然后等待委托的执行必须在{{1}之前完成,将委托的执行编组到UI线程上。返回。但是,如果您的UI线程忙于在调度和处理消息之外做其他事情,该怎么办?我可以从您的代码中看到Control.Invoke可能在这里发挥作用。你的UI线程是否偶然被ManualResetEvent调用了?如果是这样肯定是问题。由于WaitOne不会传递消息,因此它会阻止UI线程,这会在从您的工作线程调用WaitOne时导致死锁。

如果您希望Control.Invoke事件的行为与ProgressChanged一样,那么您需要调用BackgroundWorker将这些事件处理程序放到UI线程上。无论如何,这就是Control.Invoke的工作方式。当然,只要您准备好让调用者在处理BackgroundWorker事件时自己进行编组操作,就不会 完全模仿BackgroundWorker类。