【问题标题】:Creating Dialog with WebBrowser from BackgroundWorker从 BackgroundWorker 使用 WebBrowser 创建对话框
【发布时间】:2012-01-09 23:14:20
【问题描述】:

我有一个网页抓取程序。在主窗体中,您可以选择一个网站和一个客户端,然后单击 Go,它将启动一个 BackgroundWorker 线程,该线程利用 WebRequest 和 HTMLAgilityPack 来处理对 SiteX 和 ClientX 的一系列请求,每个请求是多个页面。每个 BackgroundWorker 线程都有运行的验证,如果遇到问题,它将弹出一个对话框,让用户中止线程、中止请求、忽略错误或(如果从 IDE 运行)单步执行代码。我希望这个对话框包含一个 WebBrowser 控件,以很好地显示有问题的 HTML 页面。但是,因为它是从 BackgroundWorker 线程调用的,所以我收到异常“当前线程不在单线程单元中”。

这是创建对话框的函数:

protected bool ValidatePage( bool pagePasses, string msg ) {
  if ( pagePasses == false ) {
    AbortIgnoreSuspend ais = new AbortIgnoreSuspend( responsehtml, msg );
    ais.ShowDialog();
    switch ( ais.DialogResult ) {
      case DialogResult.Abort: // Aborts entire thread
        Abort = true;
        worker.CancelAsync();
        return false;
      case DialogResult.Cancel: // Aborts this case
        Abort = true;
        return false;
      case DialogResult.Ignore: // Ignore and continue
        return true;
      case DialogResult.Retry:  // Debug
        Debug.Assert( false, "Suspending Thread" );
        return true; // Will return you to calling thread and allow you to continue
      default:
        return true;
    }
  }
  return true;
}

我发现了一些示例,其中将 ApartmentState 设置为 ApartmentState.STA 的 Thread() 将能够创建我的 WebBrowser,因此我进行了以下代码调整:

protected bool ValidatePage( bool pagePasses, string msg ) {
  if ( pagePasses == false ) {
    bool setAbort = false;
    bool assertError = false;
    bool cancelWorker = false;
    bool returnContinue = true;

    Thread th = new Thread( () => {
      AbortIgnoreSuspend ais = new AbortIgnoreSuspend( responsehtml, msg );
      ais.ShowDialog();
      switch ( ais.DialogResult ) {
        case DialogResult.Abort: // Aborts entire thread
          setAbort = true;
          cancelWorker = true;
          returnContinue = false;
          break;
        case DialogResult.Cancel: // Aborts this case
          setAbort = true;
          returnContinue = false;
          break;
        case DialogResult.Ignore: // Ignore and continue
          returnContinue = true;
          break;
        case DialogResult.Retry:  // Debug
          assertError = true;
          returnContinue = true; // Will return you to calling thread and allow you to continue
          break;
        default:
          returnContinue = true;
          break;
      }

    } );
    th.SetApartmentState( ApartmentState.STA );
    th.Start();
    th.Join();

    Abort = setAbort;
    Debug.Assert( !assertError, "Suspending thread for debugging" );
    if ( cancelWorker ) { worker.CancelAsync(); }
    return returnContinue;
  }
  return true;
}

这似乎可以工作(我已经使用单个 BackgroundWorker 进行了测试),但是我很确定由于我缺乏使用线程的经验,我在线程安全性方面犯了一些错误。我做错了什么,错过了什么?

【问题讨论】:

  • 线程方面没问题。 STA 还必须发送一个消息循环,您可以从 ShowDialog() 免费获得一个。您将遇到的最大问题是对话框有时会消失在用户看不到的另一个窗口后面。或者用户意外关闭对话框,因为她没想到会弹出一个。

标签: c# winforms multithreading browser backgroundworker


【解决方案1】:

根据您在 .NET 中创建线程的方式,.NET 会将该线程的 apartment 设置为名为 MTASTA 的野兽。 MTA 和 STA 都是 COM 技术;除了它们在 .NET 中几乎没有用处(如 MS 的 .NET 文档中所述)。如果您没有明确设置,MS 喜欢将某些 .NET 线程默认为 MTA,我怀疑您的线程不是主要的 UI STA 线程。

我看到您在 .NET 中使用了相当漂亮的 BackgroundWorker。如果您将 WorkerReportsProgress 设置为 true,则为 ProgressChanged 设置一个处理程序并将您的对话框代码移动到在这个新处理程序中调用应该可以解决您的问题。

ProgressChanged 并不一定要实际报告进度,您可以将它用于任何事情,尤其是当您需要线程上下文切换时。您传递给 ReportProgress 的参数也可以是您喜欢的任何对象。

为什么会这样? ProgressChanged 自动执行从工作线程上下文到主用户界面 (UI) 线程上下文的线程编组。只有在 UI 线程中才能安全地执行 UI 调用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多