Using a font resource with FontFamily will cause UnmanagedMemoryStream leaks
See original GitHub issueBackground
This is a known WPF issue but still affects applications created with Material Design XAML Toolkit since it relies on the Roboto font. See https://stackoverflow.com/a/31452979. The answer at StackOverflow has a broken link to a issue at Microsoft Connect. I have not been able to find out if this bug is going to be fixed. I have not found any official comments about this issue.
According to my observations the leaked object UnmanagedMemoryStream
is very small in size, about 32 bytes. This makes it almost invisible to the memory footprint of many applications. I observed this leak in an application with many bindings which are updated frequently. The application is not expected to be restarted often. These traits would lead to huge amounts of UnmanagedMemoryStream
being created over time.
How to reproduce
You need Visual Studio Diagnostics Tool, dotMemory or another memory profiler to observer the memory leak. I have used both Visual Studio Diagnostics Tool and dotMemory to track down the leak and confirm it has to do with font loading.
Visual Studio Diagnostics Tool
- Start demo project
- Take snapshot 1
- Navigate to “Fields” page
- Take snapshot 2
- Change text field “Name”
- Take snapshot 3
- Snapshot 1 will have about 162 instances of
UnmanagedMemoryStream
- Snapshot 2 will have about 36 new instances of
UnmanagedMemoryStream
- Snapshot 3 will have about 3 new instances of
UnmanagedMemoryStream
Workaround
The workaround suggested by Israel Altar is to use file based fonts instead of resource based fonts. I have confirmed that this workaround “works on my machine”.
- Create a static
FontFamily
instance:
public class MaterialDesignFonts
{
public static FontFamily Roboto { get; }
static MaterialDesignFonts()
{
var fontPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, @"Resources\Roboto\");
Roboto = new FontFamily(new Uri($"file:///{fontPath}"), "./#Roboto");
}
}
- Add the Roboto fonts to the project.
Add the fonts with “Build Action” None
and “Copy to Output Directory” Copy if newer
<None Include="Resources\Roboto\Roboto-Black.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-BlackItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Bold.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-BoldItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Italic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Light.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-LightItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Medium.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-MediumItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-Thin.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\Roboto-ThinItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-Bold.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-BoldItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-Italic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-Light.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-LightItalic.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Resources\Roboto\RobotoCondensed-Regular.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
- Change the
FontFamily
attributes on yourWindow
types. This is the only place I use the Roboto font in my application:
FontFamily="{x:Static local:MaterialDesignFonts.Roboto}"
TextElement.FontFamily="{x:Static local:MaterialDesignFonts.Roboto}"
Future
How do we handle this issue in Material Design XAML Toolkit? Possible actions below but I let you guys weigh in on the way forward.
- Write a wiki page about font usage and describe the workaround for those in need of it
- Let the library solve this issue:
- Keep the font as a resource in the library
- Add a
MaterialDesignFonts
type that takes care of writing temporary font files to disk before loading - The consumer of the library can decide if they want to use
{StaticResource MaterialDesignFont}
or{x:Static materialDesign:MaterialDesignFonts.Roboto}
Personally I think it would be nice if I could use the material design fonts without memory leakage out of the box 😃
Issue Analytics
- State:
- Created 6 years ago
- Reactions:9
- Comments:8 (7 by maintainers)
Have you seen this different solution: using a markup extension to fix memory leak
With this:
FontFamily="{local:FontExplorer Key=Roboto}">
@quicoli I have not seen that. I think I like it better, though I think i am going to leave in the option for people to include the font files in their project if they want. I dropped the
MaterialDesignFonts
static class in favor of theMaterialDesignFont
markup extension.