在.NET中创建Font对象的成本

时间:2009-11-23 20:54:51

标签: .net fonts

在我正在开发的应用程序中,我们使用DevExpress XtraGrid控件,该控件具有RowCellStyle事件,允许自定义每个单元格的样式。此事件的事件处理程序通常如下所示:

private gridView1_RowCellStyle(object sender, RowCellStyleEventArgs e)
{
    if (/* Some condition */)
    {
        e.Appearance.Font = new Font(gridView1.Appearance.Font, FontStyle.Bold);
    }
}

每次渲染单元格时都会调用此处理程序,因此它可以创建大量Font个实例。所以我想知道这样做的成本......我做了一些实验,似乎每次都会创建一个新的HFONT手柄。我应该担心吗?对资源使用的影响有多大?

如果它对性能产生重大影响,是否应该有FontCache类或类似的东西?

注意:我知道如何解决问题(我只需要创建一次字体并每次都重复使用),我的问题是关于创建许多HFONT句柄的成本

3 个答案:

答案 0 :(得分:6)

测试;我通过重用获得了双重性能(在发布,重用= 3000毫秒,重新创建= 4900毫秒)

using System.Windows.Forms;
using System.Drawing;
using System.Diagnostics;
static class Program
{
    static void Main()
    {
        Button btn1, btn2;
        Form form = new Form
        {
            Controls = {
                (btn1 = new Button { Dock = DockStyle.Bottom, Text = "reuse" }),
                (btn2 = new Button { Dock = DockStyle.Bottom, Text = "recreate"})
            }
        };
        btn1.Click += delegate
        {
            var watch = Stopwatch.StartNew();
            using (var gfx = form.CreateGraphics())
            using (var font = new Font(SystemFonts.DefaultFont, FontStyle.Bold))
            {                
                gfx.Clear(SystemColors.Control);
                for (int i = 0; i < 10000; i++)
                {
                    gfx.DrawString("abc", font, SystemBrushes.ControlText, i % 103, i % 152);
                }
            }
            watch.Stop();
            form.Text = watch.ElapsedMilliseconds + "ms";
        };
        btn2.Click += delegate
        {
            var watch = Stopwatch.StartNew();
            using (var gfx = form.CreateGraphics())
            {
                gfx.Clear(SystemColors.Control);
                for (int i = 0; i < 10000; i++)
                {
                    using (var font = new Font(SystemFonts.DefaultFont, FontStyle.Bold))
                    {
                        gfx.DrawString("abc", font, SystemBrushes.ControlText, i % 103, i % 152);
                    }
                }
            }
            watch.Stop();
            form.Text = watch.ElapsedMilliseconds + "ms";
        };
        Application.Run(form);

    }
}

答案 1 :(得分:2)

Font实现了IDisposable - 你应该确保在完成后调用Dispose。

它是一种非托管资源,因此操作系统最终可能会耗尽资源以使用更多对象来刺激您。

检查TaskManager中的GDI句柄计数,看它是否持续增加。

答案 2 :(得分:2)

重用几乎总是比按需更快,但重用或缓存Font对象的能力可能会有所不同。

如果要重复使用Font对象并将不同的Font对象与每个控件相关联,则会增加超出gdi句柄限制的机会。这将损害给定用户在发生时使用的所有应用程序。

理想情况下,您应该在应用程序/进程级别缓存Font对象,并且仅缓存必要的最小集合(最多可能是十几个)。此外,缓存一小组共享字体和其他GDI对象可以让您更好地处理棘手的消息,如WM_SETTINGCHANGE。在您的示例中,您正在修改默认值,并且当用户更改方案或Windows中的默认显示字体时,您可能会遇到绘图错误。在WM_SETTINGCHANGE期间,您会考虑发布缓存副本并初始化或准备新的字体对象集。

WM_SETTINGCHANGE (Windows) @ MSDN

如果您需要提供数百种不同的字体(或其他gdi对象),则不应缓存字体,只能根据需要创建所需内容。这不是性能友好的,但它将帮助您的应用程序与用户可能打开的其他应用程序共存。持有数百个GDI对象将极大地增加用户遇到GDI句柄限制的可能性。但是,基于引用计数的缓存可以提供最适合您的权衡,只保留最常用变体的硬拷贝。

最后,正如Matt Breckon所提到的,对所有可用的GDI对象使用IDisposable。这将帮助您避免gdi句柄泄漏。