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();
    }
  }
}

我希望这不是小题大做!我讨厌提供太多信息,因为这样我就会受到人们对我风格的批评

C#:方法Invoke从不返回

  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事件时执行自己的封送处理即可。