2

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()

Community
  • 1
  • 1
AlignedDev
  • 8,102
  • 9
  • 56
  • 91

3 Answers3

2

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.

Timores
  • 14,439
  • 3
  • 46
  • 46
  • So when I use the WPF objects, it opens a window in the background so it can do the measurements? Could you provide an example or a link to implementing your idea? – AlignedDev Mar 04 '11 at 16:16
  • Yes, it indirectly creates a window. I've updated the answer but am unable to find a solution, sorry. – Timores Mar 04 '11 at 23:31
  • +1 For your information on what the function is doing. Thank you for your help, it seems that we are using this object in a way it wasn't created to be used. – AlignedDev Mar 07 '11 at 15:52
  • Do you think wrapping this method in Dispatcher.CurrentDispatcher.Invoke would do any good? I've tried it and the code runs fine on my machine, but I haven't been able to replicate the exception locally. – AlignedDev Mar 07 '11 at 17:20
  • Calling Dispatcher.CurrentDispatcher will call the same methods as GetTextSize, so I don't see how it would behave any differently. But as I have not understood the real problem behind the original question, it's worth a try. – Timores Mar 07 '11 at 20:46
1

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:

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 };
      }));

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)

NightDweller
  • 913
  • 5
  • 8
  • I'm accepting this answer so that my bounty points don't go to waste and for pointing out using Application.Current.Dispatcher. I was close... – AlignedDev Apr 05 '11 at 11:08
  • 1
    Thanks :) If you're interested in the "Why" (why are we forced to invoke on the UI/STA thread) you can look at [this](http://msdn.microsoft.com/en-us/library/ms741870.aspx) article. The WPF UI (STA) thread has to interact with the windows GUI via COM , the underlying implementation requires that operations will be performed on the creating thread and this abstraction [leaks](http://www.joelonsoftware.com/articles/LeakyAbstractions.html) into WPF. – NightDweller Apr 05 '11 at 11:42
  • I just implemented this. Application.Current is null (at least in my UnitTest) so I added if(Application.Current == null) new Application();. Also CurrentDispatcher doesn't compile. Change it to Application.Current.Displatcher.Invoke and it works. – AlignedDev Apr 05 '11 at 21:34
  • This won't work in a unit test unless it runs in an STA thread (i am not sure if it's possible to create one inside the unit). "Application" is initialized by the framework during startup when the App.cs and App.xaml are parsed and loaded. – NightDweller Apr 06 '11 at 20:59
  • How can I initialize the Application if I'm not inside the WPF framework? I'm calling this from a service. Calling new Application() seemed to work, but then caused me problems... see the edit at the bottom of my question. – AlignedDev Apr 08 '11 at 21:47
  • It's usually not a good idea to do any UI work from a windows service. That said - the following MAY solve your problem: `Thread thread = new Thread(new ThreadStart(RunFormattingOnThisThread); thread.SetApartmentState(ApartmentState.STA); thread.Start();` Formatting should run on that thread, and you can communicate with it by having it monitor a thread-safe queue for requests. There will be no need for invoke in this case as STA threads are allowed to communicate with the COM interface for UI. – NightDweller Apr 09 '11 at 22:58
0

Have you renamed anything? If yes, check that link: WPF Prism: Problem with creating a Shell

Community
  • 1
  • 1
Shiraz Bhaiji
  • 64,065
  • 34
  • 143
  • 252