Skip to content

Commit

Permalink
Use "Random.Shared" on .NET 6+ (#907)
Browse files Browse the repository at this point in the history
Co-authored-by: Atif Aziz <[email protected]>
  • Loading branch information
leotsarev and atifaziz authored Dec 10, 2022
1 parent 4e09ca4 commit eeddf4d
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 46 deletions.
71 changes: 71 additions & 0 deletions MoreLinq/GlobalRandom.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#region License and Terms
// MoreLINQ - Extensions to LINQ to Objects
// Copyright (c) 2010 Leopold Bushkin. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#endregion

namespace MoreLinq
{
using System;
#if !NET6_0_OR_GREATER
using System.Threading;
#endif

public static partial class MoreEnumerable
{
/// <remarks>
/// <see cref="System.Random"/> is not thread-safe so the following
/// implementation uses thread-local <see cref="System.Random"/>
/// instances to create the illusion of a global
/// <see cref="System.Random"/> implementation. For some background,
/// see <a href="https://blogs.msdn.microsoft.com/pfxteam/2009/02/19/getting-random-numbers-in-a-thread-safe-way/">Getting
/// random numbers in a thread-safe way</a>.
/// On .NET 6+, delegates to <c>Random.Shared</c>.
/// </remarks>

sealed class GlobalRandom : Random
{
#if NET6_0_OR_GREATER
public static Random Instance => Shared;
#else
public static Random Instance { get; } = new GlobalRandom();

static int _seed = Environment.TickCount;
[ThreadStatic] static Random? _threadRandom;
static Random ThreadRandom => _threadRandom ??= new Random(Interlocked.Increment(ref _seed));

GlobalRandom() { }

public override int Next() => ThreadRandom.Next();
public override int Next(int minValue, int maxValue) => ThreadRandom.Next(minValue, maxValue);
public override int Next(int maxValue) => ThreadRandom.Next(maxValue);
public override double NextDouble() => ThreadRandom.NextDouble();
public override void NextBytes(byte[] buffer) => ThreadRandom.NextBytes(buffer);

protected override double Sample()
{
// All the NextXXX calls are hijacked above to use the Random
// instance allocated for the thread so no call from the base
// class should ever end up here. If Random introduces new
// virtual members in the future that call into Sample and
// which end up getting used in the implementation of a
// randomizing operator from the outer class then they will
// need to be overriden.

throw new NotImplementedException();
}
#endif
}
}
}
60 changes: 16 additions & 44 deletions MoreLinq/Random.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ namespace MoreLinq
{
using System;
using System.Collections.Generic;
using System.Threading;

public static partial class MoreEnumerable
{
Expand All @@ -29,6 +28,7 @@ public static partial class MoreEnumerable
/// </summary>
/// <returns>An infinite sequence of random integers</returns>
/// <remarks>
/// <para>
/// The implementation internally uses a shared, thread-local instance of
/// <see cref="System.Random" /> to generate a random number on each
/// iteration. The actual <see cref="System.Random" /> instance used
Expand All @@ -41,7 +41,9 @@ public static partial class MoreEnumerable
/// in the generation of the sequence of random numbers. Because the
/// <see cref="System.Random" /> instance is shared, if multiple sequences
/// are generated on the same thread, the order of enumeration affects the
/// resulting sequences.
/// resulting sequences.</para>
/// <para>
/// On .NET 6 or later, <c>System.Random.Shared</c> is used.</para>
/// </remarks>

public static IEnumerable<int> Random()
Expand Down Expand Up @@ -71,6 +73,7 @@ public static IEnumerable<int> Random(Random rand)
/// <param name="maxValue">exclusive upper bound for the random values returned</param>
/// <returns>An infinite sequence of random integers</returns>
/// <remarks>
/// <para>
/// The implementation internally uses a shared, thread-local instance of
/// <see cref="System.Random" /> to generate a random number on each
/// iteration. The actual <see cref="System.Random" /> instance used
Expand All @@ -83,7 +86,9 @@ public static IEnumerable<int> Random(Random rand)
/// in the generation of the sequence of random numbers. Because the
/// <see cref="System.Random" /> instance is shared, if multiple sequences
/// are generated on the same thread, the order of enumeration affects the
/// resulting sequences.
/// resulting sequences.</para>
/// <para>
/// On .NET 6 or later, <c>System.Random.Shared</c> is used.</para>
/// </remarks>

public static IEnumerable<int> Random(int maxValue)
Expand Down Expand Up @@ -118,6 +123,7 @@ public static IEnumerable<int> Random(Random rand, int maxValue)
/// <param name="maxValue">Exclusive upper bound of the values returned</param>
/// <returns>An infinite sequence of random integers</returns>
/// <remarks>
/// <para>
/// The implementation internally uses a shared, thread-local instance of
/// <see cref="System.Random" /> to generate a random number on each
/// iteration. The actual <see cref="System.Random" /> instance used
Expand All @@ -130,7 +136,9 @@ public static IEnumerable<int> Random(Random rand, int maxValue)
/// in the generation of the sequence of random numbers. Because the
/// <see cref="System.Random" /> instance is shared, if multiple sequences
/// are generated on the same thread, the order of enumeration affects the
/// resulting sequences.
/// resulting sequences.</para>
/// <para>
/// On .NET 6 or later, <c>System.Random.Shared</c> is used.</para>
/// </remarks>

public static IEnumerable<int> Random(int minValue, int maxValue)
Expand Down Expand Up @@ -166,6 +174,7 @@ public static IEnumerable<int> Random(Random rand, int minValue, int maxValue)
/// </summary>
/// <returns>An infinite sequence of random doubles</returns>
/// <remarks>
/// <para>
/// The implementation internally uses a shared, thread-local instance of
/// <see cref="System.Random" /> to generate a random number on each
/// iteration. The actual <see cref="System.Random" /> instance used
Expand All @@ -178,7 +187,9 @@ public static IEnumerable<int> Random(Random rand, int minValue, int maxValue)
/// in the generation of the sequence of random numbers. Because the
/// <see cref="System.Random" /> instance is shared, if multiple sequences
/// are generated on the same thread, the order of enumeration affects the
/// resulting sequences.
/// resulting sequences.</para>
/// <para>
/// On .NET 6 or later, <c>System.Random.Shared</c> is used.</para>
/// </remarks>

public static IEnumerable<double> RandomDouble()
Expand Down Expand Up @@ -215,44 +226,5 @@ static IEnumerable<T> RandomImpl<T>(Random rand, Func<Random, T> nextValue)
while (true)
yield return nextValue(rand);
}

/// <remarks>
/// <see cref="System.Random"/> is not thread-safe so the following
/// implementation uses thread-local <see cref="System.Random"/>
/// instances to create the illusion of a global
/// <see cref="System.Random"/> implementation. For some background,
/// see <a href="https://blogs.msdn.microsoft.com/pfxteam/2009/02/19/getting-random-numbers-in-a-thread-safe-way/">Getting
/// random numbers in a thread-safe way</a>
/// </remarks>

sealed class GlobalRandom : Random
{
public static readonly Random Instance = new GlobalRandom();

static int _seed = Environment.TickCount;
[ThreadStatic] static Random? _threadRandom;
static Random ThreadRandom => _threadRandom ??= new Random(Interlocked.Increment(ref _seed));

GlobalRandom() { }

public override int Next() => ThreadRandom.Next();
public override int Next(int minValue, int maxValue) => ThreadRandom.Next(minValue, maxValue);
public override int Next(int maxValue) => ThreadRandom.Next(maxValue);
public override double NextDouble() => ThreadRandom.NextDouble();
public override void NextBytes(byte[] buffer) => ThreadRandom.NextBytes(buffer);

protected override double Sample()
{
// All the NextXXX calls are hijacked above to use the Random
// instance allocated for the thread so no call from the base
// class should ever end up here. If Random introduces new
// virtual members in the future that call into Sample and
// which end up getting used in the implementation of a
// randomizing operator from the outer class then they will
// need to be overriden.

throw new NotImplementedException();
}
}
}
}
2 changes: 1 addition & 1 deletion MoreLinq/RandomSubset.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static partial class MoreEnumerable

public static IEnumerable<T> RandomSubset<T>(this IEnumerable<T> source, int subsetSize)
{
return RandomSubset(source, subsetSize, new Random());
return RandomSubset(source, subsetSize, GlobalRandom.Instance);
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion MoreLinq/Shuffle.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public static partial class MoreEnumerable

public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
return Shuffle(source, new Random());
return Shuffle(source, GlobalRandom.Instance);
}

/// <summary>
Expand Down

0 comments on commit eeddf4d

Please sign in to comment.