从BackgroundWorker使用WebBrowser创建对话框

本文关键字:创建 对话框 WebBrowser 使用 BackgroundWorker | 更新日期: 2023-09-27 18:20:01

我有一个网页抓取程序。从主表单中,您可以选择一个网站和一个客户端,然后单击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测试过),但我很确定,由于我缺乏使用线程的经验,我在线程安全性方面犯了一些错误。我做错了什么,错过了什么?

从BackgroundWorker使用WebBrowser创建对话框

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

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

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

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