Должен ли этот код C # быть реорганизован для использования класса Lazy <T>? - PullRequest
8 голосов
/ 24 августа 2011

У меня есть следующий код, который может быть вызван через несколько веб-запросов в одну секунду.Таким образом, я не хочу, чтобы второй + запрос попадал в базу данных, но ожидал, пока первый не выполнит.

Должен ли я выполнить рефакторинг для использования вместо этого Lazy<T> ключевого слова класса?Если 10 вызовов к Lazy<T> фрагменту кода происходят одновременно, ждут ли 9 из этих вызовов завершения первого?

public class ThemeService : IThemeService
{
    private static readonly object SyncLock = new object();
    private static IList<Theme> _themes;
    private readonly IRepository<Theme> _themeRepository;

    <snip snip snip>

    #region Implementation of IThemeService

    public IList<Theme> Find()
    {
        if (_themes == null)
        {
            lock (SyncLock)
            {
                if (_themes == null)
                {
                    // Load all the themes from the Db.
                    _themes = _themeRepository.Find().ToList();
                }
            }
        }

        return _themes;
    }

    <sip snip snip>

    #endregion
}

Ответы [ 2 ]

12 голосов
/ 24 августа 2011

Да, вы можете использовать Lazy<T>

С MSDN :

По умолчанию объекты Lazy являются поточно-ориентированными.То есть, если конструктор не указывает тип безопасности потока, создаваемые им объекты Lazy являются потокобезопасными.В многопоточных сценариях первый поток, обращающийся к свойству Value потоково-безопасного объекта Lazy, инициализирует его для всех последующих обращений ко всем потокам , и все потоки совместно используют одни и те же данные.Следовательно, не имеет значения, какой поток инициализирует объект, и условия гонки доброкачественные.

И да, это не ключевое слово - это класс платформы .NET, который формализует часто требуемый вариант использования для lazyинициализации и предлагает это из коробки, так что вам не нужно делать это "вручную".

8 голосов
/ 24 августа 2011

Как указал @BrokenGlass, это безопасно.Но я не смог устоять и должен был сделать тест ...

Печатается только один идентификатор потока ...

private static Lazy<int> lazyInt;

// make it slow
private int fib()
{
    Thread.Sleep(1000);
    return 0;
}

public void Test()
{
    // when run prints the thread id
    lazyInt = new Lazy<int>(
        () =>
        {
            Debug.WriteLine("ID: {0} ", Thread.CurrentThread.ManagedThreadId);
            return fib();
        });

    var t1 = new Thread(() => { var x = lazyInt.Value; });
    var t2 = new Thread(() => { var x = lazyInt.Value; });
    var t3 = new Thread(() => { var x = lazyInt.Value; });

    t1.Start();
    t2.Start();
    t3.Start();

    t1.Join();
    t2.Join();
    t3.Join();
}

Но какой из них быстрее?Из результатов я получил ...

Выполнение кода 100 раз

[   Lazy: 00:00:01.003   ]
[  Field: 00:00:01.000   ]

Выполнение кода 100000000 раз

[   Lazy: 00:00:10.516   ]
[  Field: 00:00:17.969   ]

Тестовый код:

Performance.Test("Lazy", TestAmount, false,
    () =>
    {
        var laz = lazyInt.Value;
    });

Performance.Test("Field", TestAmount, false,
    () =>
    {
        var laz = FieldInt;
    });

Метод испытания:

public static void Test(string name, decimal times, bool precompile, Action fn)
{
    if (precompile)
    {
        fn();
    }

    GC.Collect();
    Thread.Sleep(2000);

    var sw = new Stopwatch();

    sw.Start();

    for (decimal i = 0; i < times; ++i)
    {
        fn();
    }

    sw.Stop();

    Console.WriteLine("[{0,15}: {1,-15}]", name, new DateTime(sw.Elapsed.Ticks).ToString("HH:mm:ss.fff"));
}
...