我正在尝试在Windows服务中将一些HTML内容渲染为位图。
我使用System.Windows.Controls.WebBrowser执行渲染。基本的渲染设置作为独立进程使用WPF窗口托管控件可以工作,但作为服务,至少我没有得到LoadCompleted事件。
我知道我至少需要一个Dispatcher或其他消息泵循环来处理这个WPF控件。也许我做得对,只是WebBrowser控件需要额外的技巧/不兼容性。这就是我的代码:
我相信只需要运行一个Dispatcher,并且它可以在服务的生命周期内运行。我认为Dispatcher.Run()是实际的循环本身,因此需要自己的线程,否则可能会阻塞。而且,在这种情况下,该线程需要[STAThread]
。因此,在相关的静态构造函数中,我有以下内容:
var thread = new Thread(() =>
{
dispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
其中dispatcher
是一个静态字段。我认为只能有一个,但我不确定是否可以从任何地方使用Dispatcher.CurrentDispatcher()
并获取正确的引用。
渲染操作如下。我在dispatcher
的线程上创建、导航和处理WebBrowser
,但事件处理程序分配和mres.Wait
可能都发生在渲染请求处理操作中。我曾经遇到过“调用线程无法访问此对象,因为不同的线程拥有它”的问题,但现在通过这种设置,我没有遇到这个问题了。
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从未触发而导致超时异常。我尝试验证调度程序是否正常运行和传输,但不确定如何操作。我从静态构造函数钩取了一些调度程序的事件并获取了一些输出信息,所以我认为它正在工作。
该代码在wb.NavigateToStream(ms);断点处得到执行。
这是调度程序应用不当吗?还是wb.LoadCompleted未触发是由于其他原因?
谢谢!
wb.LoadCompleted
没有被触发是因为你在不同于创建 WebBrowser 对象的线程上添加了该事件处理程序。尝试将其移动到dispatcher.Invoke(() => { .. })
委托内部。这可能仍然不是一个好的设计,但至少事件应该会被触发。 - noseratio - open to work