2

因此,正如标题所示,我正在尝试在类库中使用 WebBrowser 控件。我已经经历了几个 SO 问题,比如这篇优秀的帖子,但在我的情况下,独特之处在于 WebBrowser 对象必须在应用程序的生命周期内保持活动状态,并在库客户端不时进行的不同调用中保持其状态/cookies到时间。

我已经确认 WebBrowser 控件不会进行导航,除非它创建的线程包含消息泵。但是,一旦我引入消息泵,Application.Run()调用代码块并且不会生成更多事件。任何帮助都会真正得到应用。

4

1 回答 1

3

如果我正确理解了这个问题,您需要在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.AsyncTPL 并async/await大大简化了在MessageLoopApartment's 线程中创建的对象的操作,例如_webBrowser.

导航测试在内部执行,MainForm_Load但其生命周期不受该单个调用边界的限制。两者都在里面被摧毁。测试应用程序是一个 WinForms 应用程序,但它也可能是一个控制台应用程序或其他任何东西。_webBrowser_apartmentMainForm_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();
        }

    }
}
于 2014-02-14T09:22:22.187 回答