【问题标题】:How might I create and use a WebBrowser control on a worker thread?如何在工作线程上创建和使用 WebBrowser 控件?
【发布时间】:2011-01-01 00:26:36
【问题描述】:

我正在创建一个应用程序,使用以下方法对网站进行屏幕截图http://pietschsoft.com/post/2008/07/C-Generate-WebPage-Thumbmail-Screenshot-Image.aspx

我试图使应用程序多线程,但我遇到了以下错误:

[ActiveX 控件 '8856f961-340a-11d0-a96b-00c04fd705a2' 无法实例化,因为当前线程不在单线程单元中。]

有什么建议可以解决这个问题吗?我的代码基本如下:

List<string> lststrWebSites = new List<string>();
lststrWebSites.Add("http://stackoverflow.com");
lststrWebSites.Add("http://www.cnn.com");
foreach (string strWebSite in lststrWebSites)
{
  System.Threading.ThreadStart objThreadStart = delegate
  {
    Bitmap bmpScreen = GenerateScreenshot(strWebSite, -1, -1);
    bmpScreen.Save(@"C:\" + strWebSite + ".png", 
      System.Drawing.Imaging.ImageFormat.Png);
  };
  new System.Threading.Thread(objThreadStart).Start();
}

GenerateScreenShot() 函数实现是从上面的 URL 复制过来的:

public Bitmap GenerateScreenshot(string url)
{
  // This method gets a screenshot of the webpage
  // rendered at its full size (height and width)
  return GenerateScreenshot(url, -1, -1);
}

public Bitmap GenerateScreenshot(string url, int width, int height)
{
  // Load the webpage into a WebBrowser control
  WebBrowser wb = new WebBrowser();
  wb.ScrollBarsEnabled = false;
  wb.ScriptErrorsSuppressed = true;
  wb.Navigate(url);
  while (wb.ReadyState != WebBrowserReadyState.Complete) 
    { Application.DoEvents(); }


  // Set the size of the WebBrowser control
  wb.Width = width;
  wb.Height = height;

  if (width == -1)
  {
    // Take Screenshot of the web pages full width
    wb.Width = wb.Document.Body.ScrollRectangle.Width;
  }

  if (height == -1)
  {
    // Take Screenshot of the web pages full height
    wb.Height = wb.Document.Body.ScrollRectangle.Height;
  }

  // Get a Bitmap representation of the webpage as it's rendered in 
  // the WebBrowser control
  Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
  wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
  wb.Dispose();

  return bitmap;
} 

【问题讨论】:

  • 我对您尝试在非 GUI 线程中创建和操作浏览器控件感到有些困惑。您是否有可能将繁重的工作与将繁重的工作委派给工作线程的控件的交互分开?
  • 好吧,我试图这样做,但我卡住了。

标签: c# multithreading webbrowser-control


【解决方案1】:

WebBrowser 与许多 ActiveX 控件一样,具有严格的线程要求。创建它的线程必须使用 Thread.SetApartmentState() 进行初始化才能将其切换到 STA。并且线程必须抽出一个消息循环,你从 Application.Run() 中得到一个。

这使得与浏览器的对话变得相当棘手。这是帮助您入门的代码。请注意 Completed 回调在后台线程上运行。不要忘记调用 Dispose() 来关闭线程。

using System;
using System.Threading;
using System.ComponentModel;
using System.Windows.Forms;

class WebPagePump : IDisposable {
  public delegate void CompletedCallback(WebBrowser wb);
  private ManualResetEvent mStart;
  private SyncHelper mSyncProvider;
  public event CompletedCallback Completed;

  public WebPagePump() {
    // Start the thread, wait for it to initialize
    mStart = new ManualResetEvent(false);
    Thread t = new Thread(startPump);
    t.SetApartmentState(ApartmentState.STA);
    t.IsBackground = true;
    t.Start();
    mStart.WaitOne();
  }
  public void Dispose() {
    // Shutdown message loop and thread
    mSyncProvider.Terminate();
  }
  public void Navigate(Uri url) {
    // Start navigating to a URL
    mSyncProvider.Navigate(url); 
  }
  void mSyncProvider_Completed(WebBrowser wb) {
    // Navigation completed, raise event
    CompletedCallback handler = Completed;
    if (handler != null) handler(wb);
  }
  private void startPump() {
    // Start the message loop
    mSyncProvider = new SyncHelper(mStart);
    mSyncProvider.Completed += mSyncProvider_Completed;
    Application.Run(mSyncProvider);
  }
  class SyncHelper : Form {
    WebBrowser mBrowser = new WebBrowser();
    ManualResetEvent mStart;
    public event CompletedCallback Completed;
    public SyncHelper(ManualResetEvent start) {
      mBrowser.DocumentCompleted += mBrowser_DocumentCompleted;
      mStart = start;
    }
    public void Navigate(Uri url) {
      // Start navigating
      this.BeginInvoke(new Action(() => mBrowser.Navigate(url)));
    }
    void mBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) {
      // Generated completed event
      Completed(mBrowser);
    }
    public void Terminate() {
      // Shutdown form and message loop
      this.Invoke(new Action(() => this.Close()));
    }
    protected override void SetVisibleCore(bool value) {
      if (!IsHandleCreated) {
        // First-time init, create handle and wait for message pump to run
        this.CreateHandle();
        this.BeginInvoke(new Action(() => mStart.Set()));
      }
      // Keep form hidden
      value = false;
      base.SetVisibleCore(value);
    }
  }
}

【讨论】:

  • 与问题相关,特别是这个答案,我遇到了类似的问题,并为 .NET WebBrowser 编写了一个包装器来抽象出消息循环问题。对于其他对 .NET 相当简单的无头浏览器感兴趣的人,我将代码发布到 GitHub 并通过 nuget 提供,有关更多信息,请参阅github.com/LeastOne/WebBrowserWaiter
  • 查看页面底部的许可条款。
  • @LeastOne 我在你的 github 上提出了问题,你可以看看。
【解决方案2】:

尝试设置托管浏览器控件的线程的ApartmentState

var thread = new Thread(objThreadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

【讨论】:

  • 这行得通。但我需要将 ApartmentState 设置为 STA 而不是 MTA。 thread.SetApartmentState(ApartmentState.STA);
【解决方案3】:

Main 方法的属性从STAThread 更改为MTAThread 有帮助吗?

例子:

[STAThread]
public static void Main()
{

更改为:

[MTAThread]
public static void Main()
{

【讨论】:

    猜你喜欢
    • 2012-01-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-24
    • 1970-01-01
    • 2011-07-28
    • 2014-03-21
    相关资源
    最近更新 更多