WPF FormattedText “系统找不到指定的文件”服务异常
我们使用 WPF FormattedText 对象来确定从 RSS 源获取最新新闻标题的服务中的文本大小。检索到的文本需要采用指定的画布大小。该服务每 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 []挂钩) 在 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,布尔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() 后出错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< /a>
编辑:微软的回应: “WPF 对象需要在调度程序线程上创建,而不是在线程池线程上创建。我们通常建议专用一个线程来运行调度程序循环来服务创建对象并返回的请求 他们冻僵了。谢谢,WPF 团队”〜我将如何实现这一点?
编辑:感谢 NightDweller 的最终解决方案
if(Application.Current == null) new Application();
(Size)Application.Current.Dispatcher.CurrentDispatcher.Invoke(new Func<Size>(() =>
{
...});
编辑:当我部署更改(new Application();)时,我收到一条错误记录“无法创建多个 System.Windows.Application同一 AppDomain 中的实例。” 错误来源:PresentationFramework 错误目标站点:Void .ctor()
We are using the WPF FormattedText object to determine text size in a service that grabs the latest news headlines from an RSS feed. The text retrieved needs to be in a specified canvas size. The service runs the code every 10 seconds and uses up to 2 threads if one takes longer than that. I'm using TaskFactory (which I've overridden the LimitedConcurrencyLevelTaskScheduler to limit to the amount of threads I specified).
This works great, except after several days (the length is variable), we start to get the following exceptions. The same code was working fine before we started using TPL to make it mult-threaded.
I need help figuring out what this is caused by. A few thoughts I'm looking into are: thread collisions holding on to a TTF file, memory issue, the dispatcher (see the stack trace) isn't playing nicely with the TaskFactory, other??
We don't have good profiling setup, but we've looked at the TaskManager when the exception is occurring and memory usage looks normal.
My next attempt is to use the TextBlock object and see if the exception is avoided.
Error Message: The system cannot find the file specified
Error Source: WindowsBase
Error Target Site: UInt16 RegisterClassEx(WNDCLASSEX_D)
Exception Stack Trace:
at MS.Win32.UnsafeNativeMethods.RegisterClassEx(WNDCLASSEX_D wc_d)
at MS.Win32.HwndWrapper..ctor(Int32 classStyle, Int32 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook[] hooks)
at System.Windows.Threading.Dispatcher..ctor()
at System.Windows.Threading.Dispatcher.get_CurrentDispatcher()
at System.Windows.Media.TextFormatting.TextFormatter.FromCurrentDispatcher(TextFormattingMode textFormattingMode)
at System.Windows.Media.FormattedText.LineEnumerator..ctor(FormattedText text)
at System.Windows.Media.FormattedText.DrawAndCalculateMetrics(DrawingContext dc, Point drawingOffset, Boolean getBlackBoxMetrics)
at System.Windows.Media.FormattedText.get_Metrics()
at
(my method using the FormattedText, which is in a loop)
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 };
}
EDIT: so far I've tried placing a lock around the code that calls this function, and calling it inside the CurrentDispatcher.Invoke method like so:
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 };
}));
EDIT: I've found links to others having similar, but not the exact problem.
http://www.eggheadcafe.com/software/aspnet/31783898/problem-creating-an-bitmapsource-from-an-hbitmap-in-threaded-code.aspx ~having a similar problem, but no answers
System.Windows.Media.DrawingVisual.RenderOpen() erroring after a time ~having a similar problem, but no answers
http://connect.microsoft.com/VisualStudio/feedback/details/361469/net-3-5-sp1-breaks-use-of-wpf-under-iis# ~ similar exception, but we're not using 3.5SP1 or IIS 7.
I've also submitted this through the Microsoft Connect site (please vote for it if you are having a similar problem).
https://connect.microsoft.com/WPF/feedback/details/654208/wpf-formattedtext-the-system-cannot-find-the-file-specified-exception-in-a-service
EDIT: Response from Microsoft:
"WPF objects need to be created on Dispatcher threads, not thread-pool threads. We usually recommend dedicating a thread to run the dispatcher loop to service requests to create objects and return
them frozen. Thanks, WPF Team" ~ How would I implement this?
EDIT: final solution thanks to NightDweller
if(Application.Current == null) new Application();
(Size)Application.Current.Dispatcher.CurrentDispatcher.Invoke(new Func<Size>(() =>
{
...});
EDIT: When I deployed the change (new Application();), I got an error logged " Cannot create more than one System.Windows.Application instance in the same AppDomain."
Error Source: PresentationFramework
Error Target Site: Void .ctor()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
黑暗中的一枪:
堆栈跟踪似乎表明 WPF 在执行 GetTextSize 的线程中找不到 Dispatcher,因此它必须创建一个新的 Dispatcher,这涉及到创建窗口句柄。
每 10 秒调用一次意味着 8'640 个线程,即每天的窗口数。根据 Mark Russinovich 的说法,有一个限制每个会话 32 K 窗口,这可以解释 RegisterClassEx 中的错误。
克服这个问题的一个想法是从主线程读取当前的调度程序并将其设置在任务中。
编辑:
我又看了一眼,似乎无法设置线程的调度程序(它是自动创建的)。
抱歉,我无法理解这里发生了什么。
为了计算文本大小,WPF 需要一个 FormattedText 实例,该实例存储为 Dispatcher 类的成员。现有的调度程序存储在弱引用列表中。每一个都与一个特定的线程相关联。
在这里,看起来新的 Dispatcher 实例被创建了很多很多次。
因此,要么调用线程是新的,要么内存非常低并且弱引用已被丢弃。
第一种情况(新线程)不太可能,因为任务调度程序使用线程池,每个核心大约有 25 个线程(如果我没记错的话),这不足以耗尽 ATOM 或窗口池。
在第二种情况下,资源耗尽的可能性不大,因为 HwndWrapper 是 IDisposable,并且 Dispose 方法负责释放已注册的类。
A shot in the dark:
The stack trace seems to show that WPF does not find a Dispatcher in the thread executing GetTextSize, so it has to create a new one, which involves creating a handle to a window.
Calling this every 10 seconds means 8'640 threads, thus windows per day. According to Mark Russinovich, there is a limit of 32 K windows per session, which may explain the error in RegisterClassEx.
An idea to overcome this is to read the current dispatcher from your main thread and set it in your tasks.
Edit:
I had another look and it looks like one cannot set the Dispatcher of a thread (it's created automatically).
I'm sorry, I am unable to understand what is going on here.
In order to compute the text size, WPF needs a FormattedText instance, which is stored as a member of the Dispatcher class. The existing Dispatchers are stored in a list of weak references. Each one is associated with a specific thread.
Here, it looks like new Dispatcher instances are created many, many times.
So, either the calling thread is new or memory is quite low and the weak references have been discarded.
The first case (new thread) is unlikely as the task scheduler uses the thread pool, which has about 25 threads per core (if I remember correctly), which is not enough to deplete the pool of ATOMs or windows.
In the second case, the depletion of resource is unlikely as the HwndWrapper is IDisposable and the Dispose method takes care of freeing the registered class.
从您提供的信息中您已经知道,所有 UI 元素(FormattedText 就是其中之一)都必须在 UI 线程上创建。
您正在寻找的代码是:
注意 Application.Current - 您需要“Application”调度程序,它是 WPF 应用程序中 UI 线程的调度程序。
您当前的代码实际上为当前线程创建了一个调度程序,因此您并没有真正更改执行线程(请参阅此处 关于调度程序)
As you already know from the info you provided, All UI elements (FormattedText is one) have to be created on the UI thread.
The code you are looking for is:
Notice the Application.Current - you want the "Application" dispatcher which is the dispatcher for the UI thread in WPF applications.
Your current code actually creates a dispatcher for the current thread so you didn't really change the executing thread (see here regarding the dispatcher)
你重命名过什么吗?如果是,请检查该链接:WPF Prism:创建问题外壳
Have you renamed anything? If yes, check that link: WPF Prism: Problem with creating a Shell