Thread Safe Random in C♯

Posted March 16th, 2021 in c-sharp

The C♯ class to generate random numbers, System.Random is not thread safe. If you share the same Random instance across threads things start to break.

Microsoft offer some solutions for this (for example locking), but if you just want to generate some random numbers and don't need them to follow a predictable stream, there are simpler (and safer) solutions.

Using System.Guid

You can use System.Guid for random, with a catch: some bytes of a guid are not completely random, as they are masked with constants (see Guid.Unix.cs). To get around that, we must start at byte 10 of the Guid, which is where the "node" portion starts: https://en.wikipedia.org/wiki/Universally_unique_identifier#Format

Here's an implementation to use byte 10 onwards to generate a random int:

public static int RandomInt()
{
  return BitConverter.ToInt32(Guid.NewGuid().ToByteArray(), 10);
}

To see the effect of the masking on the bytes returned from System.Guid, here's a method showcasing the problem:

// Do not use! Results all begin with a 4 or a 5 because of masking
public static long RandomLong()
{
  return BitConverter.ToInt64(Guid.NewGuid().ToByteArray(), 0);
}

The results of the above method all begin with a 4 or a 5, because of the masking on the 7th, 8th and 9th bytes: https://dotnetfiddle.net/7NN0q7

Since you have to be careful around how a guid is formatted, they should only be used for random where "good enough" random will do. A one-liner to calculate jitter or shuffling an array, for example:

Enumerable.Range(0, 1000).OrderBy(x => Guid.NewGuid());

Using RandomNumberGenerator

As the name suggests, this class provides high quality and thread-safe randomness that is suitable for cryptographic purposes.

The class provides a dedicated method to generate a random integer:

public static int RandomInt()
{
  return RandomNumberGenerator.GetInt32(0, int.MaxValue);
}

And you can use the fill method to generate a random long:

public static long RandomLong()
{
  var buffer = new byte[8];
  RandomNumberGenerator.Fill(buffer);
  return BitConverter.ToInt64(buffer, 0);
}

The Fill method of RandomNumberGenerator will accept a buffer of any size and fill it with random bytes, so this technique can be used with any type that can be created from a byte array.

A final example, shuffling an array using RandomNumberGenerator:

Enumerable.Range(0, 1000).OrderBy(x => RandomNumberGenerator.GetInt32(0, int.MaxValue));

Tagged random byte generate guid class array thread c♯ microsoft locking

Comments

Please click here to load comments.