Вы Всё Ещё Используете new Random()? Окончание
Начало
Проблема с Random в .NET Framework
Основных проблем с Random в .NET Framework две:
1. При вызове new Random() в .NET Framework он использует системные часы для посева;
2. Системные часы имеют конечное разрешение.
Т.е. при быстром последовательном вызове new Random() в .NET Framework могут возникнуть два экземпляра Random с одинаковым начальным значением, что означает, что экземпляры Random вернут идентичную последовательность чисел:
// <TargetFramework>net48</TargetFramework>
Parallel.For(0, 10, x =>
{
Random rnd = new();
var value = rnd.Next();
Console.WriteLine(value);
});
При выполнении этого кода несколько чисел (а то и все) могут быть одинаковыми, а это не то, чего мы хотим.
Решение
Создать один экземпляр Random, который используется исключительно для предоставления начальных значений для остальных экземпляров Random. Поскольку начальные значения случайны, экземпляры Random не коррелируют, и вы можете полностью избежать описанной выше проблемы. Этот подход к инициализации используется по умолчанию в .NET Core, поэтому проблема возникает только в .NET Framework.
Мы можем реализовать тот же подход в классе-обёртке ThreadSafeRandom, показанном ниже. Этот класс использует [ThreadStatic], избегая проблем с инициализацией в .NET Framework:
internal static class ThreadSafeRandom
{
[ThreadStatic]
private static Random? _local;
private static readonly
Random Global = new Random();
private static Random Instance
{
get
{
if (_local is null)
{
int seed;
// избегаем конкурентного доступа
lock (Global)
{
seed = Global.Next();
}
_local = new Random(seed);
}
return _local;
}
}
public static int Next() => Instance.Next();
}
Эту потокобезопасную реализацию, можно использовать во всех версиях фреймворка:
Parallel.For(0, 10, x =>
{
var value = ThreadSafeRandom.Next();
Console.WriteLine(value);
});
Если вы используете .NET6+, рекомендуется использовать встроенный Random.Shared. В более ранних версиях вы можете использовать приведённый выше ThreadSafeRandom для решения этих проблем. Если вы ориентируетесь как на .NET6+, так и на .NET Framework, можно использовать директивы #if для разделения реализации в зависимости от целевой среды.
Источник: https://andrewlock.net/building-a-thread-safe-random-implementation-for-dotnet-framework/