Thread Safe Random in C♯

šŸ”– c-sharp ā²ļø 1 minute to read

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));

šŸ·ļø random byte guid thread class array c♯ microsoft locking stream catch constant unix node int

ā¬…ļø Previous post: Spotting Fake Indie Game Key Requests

āž”ļø Next post: Serving Static Websites Using Lambda@Edge

šŸŽ² Random post: Adding Custom Map Checks in UE4

Comments

Please click here to load comments.