我们正在使用 WPF FormattedText 对象来确定从 RSS 源获取最新新闻标题的服务中的文本大小。检索到的文本需要采用指定的 Canvas 大小。该服务每 10 秒运行一次代码,如果一个线程花费的时间超过该值,则最多使用 2 个线程。我正在使用 TaskFactory(我已重写 LimitedConcurrencyLevelTaskScheduler 以限制我指定的线程数量)。
这非常有效,除了几天后(长度可变),我们开始出现以下异常。在我们开始使用 TPL 使其成为多线程之前,相同的代码运行良好。
我需要帮助弄清楚这是由什么引起的。我正在研究的一些想法是:保存 TTF 文件的线程冲突、内存问题、调度程序(参见堆栈跟踪)与 TaskFactory 不能很好地配合,其他? 我们没有良好的分析设置,但我们在异常发生且内存使用情况看起来正常时查看了 TaskManager。 我的下一次尝试是使用 TextBlock 对象并查看是否避免了异常。
错误信息:系统找不到指定的文件 错误来源:WindowsBase 错误目标站点:UInt16 RegisterClassEx(WNDCLASSEX_D)
异常堆栈跟踪:
位于 MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d) 在MS.Win32.HwndWrapper ..ctor(Int32 classStyle,Int32样式,Int32 exStyle,Int32 x,Int32 y,Int32宽度,Int32高度,字符串名称,IntPtr父级,HwndWrapperHook [] Hook ) 在 System.Windows.Threading.Dispatcher..ctor() 在 System.Windows.Threading.Dispatcher.get_CurrentDispatcher() 在 System.Windows.Media.TextFormatting.TextFormatter.FromCurrentDispatcher(TextFormattingMode textFormattingMode) 在 System.Windows.Media.FormattedText.LineEnumerator..ctor(FormattedText 文本) 在System.Windows.Media.FormattedText.DrawAndCalculateMetrics(DrawingContext dc,点drawingOffset, bool getBlackBoxMetrics) 在 System.Windows.Media.FormattedText.get_Metrics() 在 (我的方法使用 FormattedText,它处于循环中)
private static Size GetTextSize(string txt, Typeface tf, int size)
{
FormattedText ft = new FormattedText(txt, new CultureInfo("en-us"), System.Windows.FlowDirection.LeftToRight, tf, (double)size, System.Windows.Media.Brushes.Black, null, TextFormattingMode.Display);
return new Size { Width = ft.WidthIncludingTrailingWhitespace, Height = ft.Height };
}
编辑:到目前为止,我已经尝试在调用此函数的代码周围放置一个锁,并在 CurrentDispatcher.Invoke 方法内调用它,如下所示:
return (Size)Dispatcher.CurrentDispatcher.Invoke(new Func<Size>(() =>
{
FormattedText ft = new FormattedText(txt, new CultureInfo("en-us"), System.Windows.FlowDirection.LeftToRight, tf, (double)size, System.Windows.Media.Brushes.Black, null, TextFormattingMode.Display);
return new Size { Width = ft.WidthIncludingTrailingWhitespace, Height = ft.Height };
}));
编辑:我发现其他人有类似问题的链接,但不是确切的问题。 http://www.eggheadcafe.com/software/aspnet/31783898/problem-creating-an-bitmapsource-from-an-hbitmap-in-threaded-code.aspx ~有类似的问题,但没有答案
System.Windows.Media.DrawingVisual.RenderOpen() erroring after a time ~有类似的问题,但没有答案
http://connect.microsoft.com/VisualStudio/feedback/details/361469/net-3-5-sp1-breaks-use-of-wpf-under-iis# ~ 类似的异常,但我们没有使用 3.5SP1 或 IIS 7。
我还通过 Microsoft Connect 站点提交了此问题(如果您遇到类似问题,请投票)。 https://connect.microsoft.com/WPF/feedback/details/654208/wpf-formattedtext-the-system-cannot-find-the-file-specified-exception-in-a-service
编辑:微软的回应: “WPF 对象需要在调度程序线程上创建,而不是在线程池线程上创建。我们通常建议专用一个线程来运行调度程序循环来服务创建对象并返回的请求 他们冻僵了。谢谢,WPF 团队”~我将如何实现这个?
编辑:最终解决方案感谢 NightDweller
if(Application.Current == null) new Application();
(Size)Application.Current.Dispatcher.CurrentDispatcher.Invoke(new Func<Size>(() =>
{
...});
编辑:当我部署更改(new Application();)时,我收到一条错误记录“无法在同一 AppDomain 中创建多个 System.Windows.Application 实例。” 错误来源:PresentationFramework 错误目标站点:Void .ctor()
最佳答案
黑暗中的一枪:
堆栈跟踪似乎显示 WPF 在执行 GetTextSize 的线程中找不到 Dispatcher,因此它必须创建一个新的 Dispatcher,这涉及到创建窗口句柄。
每 10 秒调用一次意味着 8'640 个线程,即每天的窗口数。根据Mark Russinovich ,每个 session 有 32 K 窗口的限制,这可以解释 RegisterClassEx 中的错误。
克服这个问题的一个想法是从主线程读取当前的调度程序并将其设置在任务中。
编辑: 我又看了一眼,似乎无法设置线程的调度程序(它是自动创建的)。
抱歉,我无法理解这里发生了什么。
为了计算文本大小,WPF 需要一个 FormattedText 实例,该实例存储为 Dispatcher 类的成员。现有的调度程序存储在弱引用列表中。每一个都与一个特定的线程相关联。 在这里,看起来新的 Dispatcher 实例被创建了很多很多次。 因此,要么调用线程是新的,要么内存非常低并且弱引用已被丢弃。 第一种情况(新线程)不太可能,因为任务调度程序使用线程池,每个核心大约有 25 个线程(如果我没记错的话),这不足以耗尽 ATOM 或窗口池。 在第二种情况下,资源耗尽的可能性不大,因为 HwndWrapper 是 IDisposable,并且 Dispose 方法负责释放已注册的类。
关于服务中的 WPF FormattedText "The system cannot find the file specified"异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5195808/