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

发布于 2024-11-18 08:23:13 字数 3081 浏览 3 评论 0原文

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

线程运行得很好,直到我调用该行,“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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

嘦怹 2024-11-25 08:23:13
  worker.RunWorkerAsync(tList[i]);
  //...
  mre.WaitOne();

这肯定会陷入僵局。您传递给 Control.Begin/Invoke() 的委托只能在 UI 线程空闲且重新进入消息循环时运行。您的 UI 线程没有空闲,它在 WaitOne() 调用上被阻塞。在工作线程完成之前,该调用无法完成。在 Invoke() 调用完成之前,您的工作线程无法完成。在 UI 线程空闲之前,该调用无法完成。僵城。

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

  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.

葬心 2024-11-25 08:23:13

您可能已使 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 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文