使用BackgroundWorker中的WebBrowser创建对话框

时间:2012-01-09 23:14:21

标签: c# winforms multithreading browser backgroundworker

我有一个网络抓取程序。从主窗体中,您可以选择一个网站和一个客户端,然后单击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;
}

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

1 个答案:

答案 0 :(得分:0)

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

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

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

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