因此,正如标题所示,我正在尝试在类库中使用 WebBrowser 控件。我已经经历了几个 SO 问题,比如这篇优秀的帖子,但在我的情况下,独特之处在于 WebBrowser 对象必须在应用程序的生命周期内保持活动状态,并在库客户端不时进行的不同调用中保持其状态/cookies到时间。
我已经确认 WebBrowser 控件不会进行导航,除非它创建的线程包含消息泵。但是,一旦我引入消息泵,Application.Run()
调用代码块并且不会生成更多事件。任何帮助都会真正得到应用。
因此,正如标题所示,我正在尝试在类库中使用 WebBrowser 控件。我已经经历了几个 SO 问题,比如这篇优秀的帖子,但在我的情况下,独特之处在于 WebBrowser 对象必须在应用程序的生命周期内保持活动状态,并在库客户端不时进行的不同调用中保持其状态/cookies到时间。
我已经确认 WebBrowser 控件不会进行导航,除非它创建的线程包含消息泵。但是,一旦我引入消息泵,Application.Run()
调用代码块并且不会生成更多事件。任何帮助都会真正得到应用。
如果我正确理解了这个问题,您需要在WebBrowser
库的整个生命周期内运行一个控件实例,并使其在具有自己的 WinForms 消息循环的专用 STA 线程上保持活跃和独立。
下面的代码显示了如何使用名为MessageLoopApartment
. 请注意如何在WebBrowser
单独的线程上创建和操作。
任务并行库在完成同步工作方面非常方便。在 STA 线程上调度的任务MessageLoopApartment.Run
可以与 同步task.Wait()
或异步等待await task
,结果和异常通过Task.Result
/从 STA 线程传播Task.Execption
,异常在调用者的堆栈帧上重新抛出。
的实现MessageLoopApartment
与 NET 4.0 兼容,它不使用任何 .NET 4.5 功能。客户端代码(WebBrowser
导航测试)可以选择使用.NET 4.0 async/await
,这可能需要。Microsoft.Bcl.Async
TPL 并async/await
大大简化了在MessageLoopApartment
's 线程中创建的对象的操作,例如_webBrowser
.
导航测试在内部执行,MainForm_Load
但其生命周期不受该单个调用边界的限制。两者都在里面被摧毁。测试应用程序是一个 WinForms 应用程序,但它也可能是一个控制台应用程序或其他任何东西。_webBrowser
_apartment
MainForm_FormClosed
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinForms_21772632
{
// https://stackoverflow.com/q/21772632/1768303
public partial class MainForm : Form
{
MessageLoopApartment _apartment;
// _webBrowser is created on a separate thread,
// with MessageLoopApartment.Run
WebBrowser _webBrowser;
// MainForm
public MainForm()
{
InitializeComponent();
// create an independent STA thread
_apartment = new MessageLoopApartment();
// create a WebBrowser on that STA thread
_webBrowser = _apartment.Run(
() => new WebBrowser(),
CancellationToken.None).Result;
this.Load += MainForm_Load;
this.FormClosed += MainForm_FormClosed;
}
// navigation test
async void MainForm_Load(object senderLoad, EventArgs eLoad)
{
// navigate
var cts = new CancellationTokenSource(10000); // cancel in 10s
var url = "http://example.com";
var html = await _apartment.Run(async () =>
{
WebBrowserDocumentCompletedEventHandler handler = null;
var navigateTcs = new TaskCompletionSource<bool>();
handler = (s, e) =>
navigateTcs.TrySetResult(true);
_webBrowser.DocumentCompleted += handler;
try
{
using (cts.Token.Register(() => navigateTcs.TrySetCanceled()))
{
_webBrowser.Navigate(url);
await navigateTcs.Task;
return _webBrowser.Document.Body.OuterHtml;
}
}
finally
{
_webBrowser.DocumentCompleted -= handler;
}
},
cts.Token);
// show the HTML of the downloaded page
MessageBox.Show(html);
}
void MainForm_FormClosed(object sender, FormClosedEventArgs e)
{
// destroy the WebBrowser
_apartment.Run(
() => _webBrowser.Dispose(),
CancellationToken.None).Wait();
// shut down the appartment
_apartment.Dispose();
}
}
/// <summary>MessageLoopApartment</summary>
public class MessageLoopApartment : IDisposable
{
Thread _thread; // the STA thread
TaskScheduler _taskScheduler; // the STA thread's task scheduler
public TaskScheduler TaskScheduler { get { return _taskScheduler; } }
/// <summary>MessageLoopApartment constructor</summary>
public MessageLoopApartment()
{
var tcs = new TaskCompletionSource<TaskScheduler>();
// start an STA thread and gets a task scheduler
_thread = new Thread(startArg =>
{
EventHandler idleHandler = null;
idleHandler = (s, e) =>
{
// handle Application.Idle just once
Application.Idle -= idleHandler;
// return the task scheduler
tcs.SetResult(TaskScheduler.FromCurrentSynchronizationContext());
};
// handle Application.Idle just once
// to make sure we're inside the message loop
// and SynchronizationContext has been correctly installed
Application.Idle += idleHandler;
Application.Run();
});
_thread.SetApartmentState(ApartmentState.STA);
_thread.IsBackground = true;
_thread.Start();
_taskScheduler = tcs.Task.Result;
}
/// <summary>shutdown the STA thread</summary>
public void Dispose()
{
if (_taskScheduler != null)
{
var taskScheduler = _taskScheduler;
_taskScheduler = null;
// execute Application.ExitThread() on the STA thread
Task.Factory.StartNew(
() => Application.ExitThread(),
CancellationToken.None,
TaskCreationOptions.None,
taskScheduler).Wait();
_thread.Join();
_thread = null;
}
}
/// <summary>A wrapper around Task.Factory.StartNew</summary>
public Task Run(Action action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
/// <summary>A wrapper around Task.Factory.StartNew to run lambdas with a result</summary>
public Task<TResult> Run<TResult>(Func<TResult> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler);
}
/// <summary>A wrapper around Task.Factory.StartNew to run async lambdas</summary>
public Task Run(Func<Task> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
/// <summary>A wrapper around Task.Factory.StartNew to run async lambdas with a result</summary>
public Task<TResult> Run<TResult>(Func<Task<TResult>> action, CancellationToken token)
{
return Task.Factory.StartNew(action, token, TaskCreationOptions.None, _taskScheduler).Unwrap();
}
}
}