[英]How might I create and use a WebBrowser control on a worker thread?
I am creating an application that does screen shots of websites using the following method http://pietschsoft.com/post/2008/07/C-Generate-WebPage-Thumbmail-Screenshot-Image.aspx 我正在创建一个应用程序,使用以下方法对网站进行屏幕截图http://pietschsoft.com/post/2008/07/C-Generate-WebPage-Thumbmail-Screenshot-Image.aspx
I tried to make the application multithreaded but I have run into the following error: 我试图使应用程序多线程,但我遇到了以下错误:
[ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment.] [ActiveX控件'8856f961-340a-11d0-a96b-00c04fd705a2'无法实例化,因为当前线程不在单线程单元中。
Any suggestions how to fix this issue? 有任何建议如何解决这个问题? My code is basically as follows: 我的代码基本如下:
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();
}
The GenerateScreenShot() function implementation is copied from the above URL: 从上面的URL复制GenerateScreenShot()函数实现:
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;
}
WebBrowser, like many ActiveX controls, has strict threading requirements. 与许多ActiveX控件一样,WebBrowser具有严格的线程要求。 The thread that creates it must be initialized with Thread.SetApartmentState() to switch it to STA. 必须使用Thread.SetApartmentState()初始化创建它的线程,以将其切换到STA。 And the thread must pump a message loop, you get one from Application.Run(). 并且线程必须泵出一个消息循环,你从Application.Run()得到一个。
That makes talking to the browser pretty tricky. 这使得与浏览器交谈非常棘手。 Here's code to get you started. 这是让你入门的代码。 Beware that the Completed callback runs on a background thread. 请注意,已完成的回调在后台线程上运行。 Don't forget to call Dispose() to shut down the thread. 不要忘记调用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);
}
}
}
Try setting the ApartmentState of the thread hosting the browser control: 尝试设置托管浏览器控件的线程的ApartmentState :
var thread = new Thread(objThreadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
Does changing the attribute on your Main
method from STAThread
to MTAThread
help? 将Main
方法的属性从STAThread
更改为MTAThread
帮助?
Example: 例:
[STAThread]
public static void Main()
{
changes to: 更改为:
[MTAThread]
public static void Main()
{
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.