2

我们正在使用 WPF FormattedText 对象来确定从 RSS 提要中获取最新新闻标题的服务中的文本大小。检索到的文本需要采用指定的画布大小。该服务每 10 秒运行一次代码,如果一个线程需要更长的时间,则最多使用 2 个线程。我正在使用 TaskFactory(我已经覆盖了 LimitedConcurrencyLevelTask​​Scheduler 以限制我指定的线程数量)。

这很好用,除了几天后(长度可变),我们开始得到以下异常。在我们开始使用 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 style, Int32 exStyle, Int32 x, Int32 y, Int32 width, Int32 height, String name, IntPtr parent, HwndWrapperHook [] 钩子)在 System.Windows.Threading.Dispatcher.get_CurrentDispatcher() 在 System.Windows.Media.TextFormatting.TextFormatter.FromCurrentDispatcher(TextFormattingMode textFormattingMode) 在 System.Windows 的 System.Windows.Threading.Dispatcher..ctor()。 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 (我的方法使用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() 一段时间后出错〜有类似的问题,但没有答案

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

编辑:来自 Microsoft 的回复:“WPF 对象需要在 Dispatcher 线程上创建,而不是线程池线程。我们通常建议使用一个线程来运行 Dispatcher 循环来服务请求以创建对象并将它们冻结返回。谢谢,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()

4

3 回答 3

2

盲目猜测:

堆栈跟踪似乎表明 WPF 在执行 GetTextSize 的线程中没有找到 Dispatcher,因此它必须创建一个新的 Dispatcher,这涉及创建窗口句柄。

每 10 秒调用一次意味着 8'640 个线程,即每天的窗口。根据Mark Russinovich的说法,每个会话的窗口限制为 32 K,这可以解释 RegisterClassEx 中的错误。

克服这个问题的一个想法是从主线程读取当前调度程序并将其设置在您的任务中。

编辑:我又看了看,似乎无法设置线程的调度程序(它是自动创建的)。

对不起,我无法理解这里发生了什么。

为了计算文本大小,WPF 需要一个 FormattedText 实例,该实例存储为 Dispatcher 类的成员。现有的 Dispatcher 存储在弱引用列表中。每一个都与一个特定的线程相关联。在这里,看起来新的 Dispatcher 实例被创建了很多很多次。因此,要么调用线程是新的,要么内存非常低并且弱引用已被丢弃。第一种情况(新线程)不太可能,因为任务调度程序使用线程池,每个内核大约有 25 个线程(如果我没记错的话),这不足以耗尽 ATOM 或窗口池。在第二种情况下,资源的耗尽是不可能的,因为 HwndWrapper 是 IDisposable 并且 Dispose 方法负责释放已注册的类。

于 2011-03-04T16:01:24.060 回答
1

从您提供的信息中您已经知道,所有 UI 元素(FormattedText 是一个)都必须在 UI 线程上创建。

您正在寻找的代码是:

return (Size)Application.Current.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 };
      }));

注意Application.Current - 您需要“Application”调度程序,它是 WPF 应用程序中 UI 线程的调度程序。您当前的代码实际上为当前线程创建了一个调度程序,因此您并没有真正更改执行线程(有关调度程序,请参见此处)

于 2011-04-05T00:19:15.370 回答
0

你改名了吗?如果是,请检查该链接:WPF Prism: Problem with creating a Shell

于 2011-03-04T15:39:48.070 回答