System.Windows.Controls.WebBrowser,System.Windows.Threading.Dispatcher和Windows服务 [英] System.Windows.Controls.WebBrowser, System.Windows.Threading.Dispatcher, and a windows service

查看:100
本文介绍了System.Windows.Controls.WebBrowser,System.Windows.Threading.Dispatcher和Windows服务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将一些html内容呈现为Windows服务中的位图。

I'm trying to render some html content to a bitmap in a Windows Service.

我正在使用System.Windows.Controls.WebBrowser进行呈现。 。基本呈现设置可以作为带有控件的WPF窗口的独立进程,但是作为一种服务,至少我没有触发LoadCompleted事件。

I'm using System.Windows.Controls.WebBrowser to perform the render. The basic rendering setup works as a standalone process with a WPF window hosting the control, but as a service, at least I'm not getting the LoadCompleted events to fire.

我知道我至少需要为此WPF控件使用Dispatcher或其他消息泵循环。也许我做得对,并且WebBrowser控件仅需要其他技巧/不兼容。这是我得到的:

I know that I at least need a Dispatcher or other message pump looping for this WPF control. Perhaps I'm doing it right and there are just additional tricks/incompatibilities necessary for the WebBrowser control. Here's what I've got:

我相信只有一个Dispatcher可以运行,并且可以在服务的整个生命周期内运行。我认为Dispatcher.Run()本身就是实际的循环,因此需要它自己的线程,否则该线程可能会阻塞。在这种情况下,该线程需要为 [STAThread] 。因此,在相关的静态构造函数中,我具有以下内容:

I believe only one Dispatcher needs to be running and that it can run for the life of the service. I believe the Dispatcher.Run() is the actual loop itself and thus needs it's own thread which it can otherwise block. And that thread needs to be [STAThread] in this scenario. Therefore, in a relevant static constructor, I have the following:

var thread = new Thread(() =>
{
    dispatcher = Dispatcher.CurrentDispatcher;

    Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

其中 dispatcher 是一个静态字段。同样,我认为只能有一个,但是我不确定是否应该可以从任何地方使用 Dispatcher.CurrentDispatcher()并获取正确的引用

where dispatcher is a static field. Again, I think there can only be one but I'm not sure if I'm supposed to be able use Dispatcher.CurrentDispatcher() from anywhere instead and get the right reference.

渲染操作如下。我在 dispatcher 的线程上创建,导航和处置 WebBrowser ,但是事件处理程序分配和 mres.Wait 我认为可能都发生在渲染请求处理操作上。我已经调用线程无法访问该对象,因为其他线程拥有它,但是现在有了这个设置,我就没有了。

The rendering operation is as follows. I create, navigate, and dispose of the WebBrowser on dispatcher's thread, but event handler assignments and mres.Wait I think may all happen on the render request-handling operation. I had gotten The calling thread cannot access this object because a different thread owns it but now with this setup I don't.

WebBrowser wb = null;
var mres = new ManualResetEventSlim();

try
{
    dispatcher.Invoke(() => { wb = new WebBrowser(); });

    wb.LoadCompleted += (s, e) =>
    {
        // Not firing
    };

    try
    {
        using (var ms = new MemoryStream())
        using (var sw = new StreamWriter(ms, Encoding.Unicode))
        {
            sw.Write(html);
            sw.Flush();
            ms.Seek(0, SeekOrigin.Begin);

            // GO!
            dispatcher.Invoke(() =>
            {
                try
                {
                    wb.NavigateToStream(ms);
                    Debug.Assert(Dispatcher.FromThread(Thread.CurrentThread) != null);
                }
                catch (Exception ex)
                {
                    // log
                }
            });

            if (!mres.Wait(15 * 1000)) throw new TimeoutException();
        }
    }
    catch (Exception ex)
    {
        // log
    }
}
finally
{
    dispatcher.Invoke(() => { if (wb != null) wb.Dispose(); });
}

运行此命令时,每次都会收到超时异常,因为LoadCompleted从不火灾。我已尝试验证调度程序是否正常运行并正常运行。不知道该怎么做,但是我从静态构造函数中钩了调度程序的一些事件,并且从中获得了一些打印输出,所以我认为它是可行的。

When I run this, I get my timeout exception every time since the LoadCompleted never fires. I've tried to verify that the dispatcher is running and pumping properly. Not sure how to do that, but I hooked a few of the dispatcher's events from the static constructor and I get some printouts from that, so I think it's working.

The代码确实到达了 wb.NavigateToStream(ms); 断点。

The code does get to a wb.NavigateToStream(ms); breakpoint.

这是Dispatcher的错误应用程序吗? wp.​​LoadCompleted是否由于其他原因而未触发?

Is this bad application of Dispatcher? Is the non-firing of wb.LoadCompleted due to something else?

谢谢!

推荐答案

这是您代码的修改版本,可用作控制台应用程序。要点:

Here's a modified version of your code which works as a console app. A few points:


  • 您需要WPF WebBrowser的父窗口。它可能是如下所示的隐藏窗口,但必须进行物理创建(即,具有实时的HWND句柄)。否则,WB将永远不会完成文档的加载( wb.Document.readyState == interactive ),并且 LoadCompleted 永远不会完成被解雇。我不知道这种行为,它与WebForm浏览器控件的WinForms版本不同。请问为什么要为这种项目选择WPF?

  • You need a parent window for WPF WebBrowser. It may be a hidden window like below, but it has to be physically created (i.e. have a live HWND handle). Otherwise, WB never finishes loading the document (wb.Document.readyState == "interactive"), and LoadCompleted never gets fired. I was not aware of such behavior and it is different from the WinForms version of WebBrowser control. May I ask why you picked WPF for this kind of project?

您确实需要添加 wb.LoadCompleted 事件处理程序位于创建WB控件的同一线程上(此处为调度程序的线程)。在内部,WPF WebBrowser只是公寓线程WebBrowser ActiveX控件的包装,该控件通过 IConnectionPointContainer 接口公开其事件。规则是,对单元线程COM对象的所有调用都必须在(或代理到)对象最初在其上创建的线程上进行,因为这正是此类对象所期望的。从这个意义上说, IConnectionPointContainer 方法与WB的其他方法没有什么不同。

You do need to add the wb.LoadCompleted event handler on the same thread the WB control was created (the dispatcher's thread here). Internally, WPF WebBrowser is just a wrapper around apartment-threaded WebBrowser ActiveX control, which exposes its events via IConnectionPointContainer interface. The rule is, all calls to an apartment-threaded COM object must be made on (or proxied to) the thread the object was originally created on, because that's what such kind of objects expect. In that sense, IConnectionPointContainer methods are no different to other methods of WB.

一个较小的方法, StreamWriter 自动关闭初始化的流(除非在构造函数中明确告知不要这样做),因此无需使用使用。

A minor one, StreamWriter automatically closes the stream it's initialized with (unless explicitly told to not do so in the constructor), so there is no need to for wrapping the stream with using.

代码已准备好编译和运行(需要一些额外的代码)程序集参考:PresentationFramework,WindowsBase,System.Windows,System.Windows.Forms,Microsoft.mshtml)。

The code is ready to compile and run (it requires some extra assembly references: PresentationFramework, WindowsBase, System.Windows, System.Windows.Forms, Microsoft.mshtml).

using System;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Windows;
using System.Windows.Threading;
using System.Windows.Controls;
using System.IO;
using System.Runtime.InteropServices;
using mshtml;    

namespace ConsoleWpfApp
{
    class Program
    {
        static Dispatcher dispatcher = null;
        static ManualResetEventSlim dispatcherReady = new ManualResetEventSlim();

        static void StartUIThread()
        {
            var thread = new Thread(() =>
            {
                Debug.Print("UI Thread: {0}", Thread.CurrentThread.ManagedThreadId);
                try
                {
                    dispatcher = Dispatcher.CurrentDispatcher;
                    dispatcherReady.Set();
                    Dispatcher.Run();
                }
                catch (Exception ex)
                {
                    Debug.Print("UI Thread exception: {0}", ex.ToString());
                }
                Debug.Print("UI Thread exits");
            });
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }

        static void DoWork()
        {
            Debug.Print("Worker Thread: {0}", Thread.CurrentThread.ManagedThreadId);

            dispatcherReady.Wait(); // wait for the UI tread to initialize

            var mres = new ManualResetEventSlim();
            WebBrowser wb = null;
            Window window = null; 

            try
            {    
                var ms = new MemoryStream();
                using (var sw = new StreamWriter(ms, Encoding.Unicode)) // StreamWriter automatically closes the steam
                {
                    sw.Write("<b>Hello, World!</b>");
                    sw.Flush();
                    ms.Seek(0, SeekOrigin.Begin);

                    // GO!
                    dispatcher.Invoke(() => // could do InvokeAsync here as then we wait anyway
                    {
                        Debug.Print("Invoke Thread: {0}", Thread.CurrentThread.ManagedThreadId);
                        // create a hidden window with WB
                        window = new Window()
                        {
                            Width = 0,
                            Height = 0,
                            Visibility = System.Windows.Visibility.Hidden,
                            WindowStyle = WindowStyle.None,
                            ShowInTaskbar = false,
                            ShowActivated = false
                        };
                        window.Content = wb = new WebBrowser();
                        window.Show();
                        // navigate
                        wb.LoadCompleted += (s, e) =>
                        {
                            Debug.Print("wb.LoadCompleted fired;");
                            mres.Set(); // singal to the Worker thread
                        };
                        wb.NavigateToStream(ms);
                    });

                    // wait for LoadCompleted
                    if (!mres.Wait(5 * 1000))
                        throw new TimeoutException();

                    dispatcher.Invoke(() =>
                    {   
                        // Show the HTML
                        Console.WriteLine(((HTMLDocument)wb.Document).documentElement.outerHTML);
                    });    
                }
            }
            catch (Exception ex)
            {
                Debug.Print(ex.ToString());
            }
            finally
            {
                dispatcher.Invoke(() => 
                {
                    if (window != null)
                        window.Close();
                    if (wb != null) 
                        wb.Dispose();
                });
            }
        }

        static void Main(string[] args)
        {
            StartUIThread();
            DoWork();
            dispatcher.InvokeShutdown(); // shutdown UI thread
            Console.WriteLine("Work done, hit enter to exit");
            Console.ReadLine();
        }
    }
}

这篇关于System.Windows.Controls.WebBrowser,System.Windows.Threading.Dispatcher和Windows服务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆