Easy Thread-Safe Random Values in C# – A Deep Dive with Tim Corey
Generating random numbers is one of those tasks every developer encounters. While creating a simple random value might seem trivial, ensuring thread safety in C#, especially in multi-threaded environments, can be tricky. In his video “Easy Thread-Safe Random Values in C#”, Tim Corey explores how random numbers work in C#, the pitfalls of older methods, and how to achieve thread safety when working with multiple threads.
In this article, we’ll follow Tim’s explanation, providing a detailed look at thread-safe random number generation, best practices, and related synchronization techniques.
Introduction to Random Values and Thread Safety in C#
Tim begins by pointing out that generating random values in C# is generally straightforward. However, there are different ways of doing things, and the choice depends on whether your code will run in one thread or multiple threads concurrently. Tim emphasizes that this video is designed as a quick, practical guide for developers, giving both simple examples and deeper insights into thread safety.
He demonstrates his examples in a console application using Visual Studio 2026 preview with .NET 10, but reassures viewers that the same code works in Visual Studio 2022 and .NET 9. For those who want to follow along, Tim provides the source code in the description.
Traditional Random Number Generation in C#
Tim starts by showing the classic approach to generating random numbers using the Random class. He creates two instances, RNG1 and RNG2, and demonstrates generating random integers with Next().
Random rng1 = new();
Random rng2 = new();
for(int i = 0; i < 10; i++)
{
int output1 = rng1.Next(1, 101);
int output2 = rng2.Next(1, 101);
Console.WriteLine($"Random 1: {output1}, Random 2: {output2}");
}Tim explains that running this code produces different numbers for each instance, but this code is not thread safe. In a multi-threaded environment, if two threads access the same Random instance, you can get unexpected values or duplicates, because the internal state of the Random object may be accessed simultaneously.
He points out that one way to ensure thread safety is to use one Random instance per thread. This is important for worker threads, async methods, or any situation where multiple threads may be running code concurrently. Sharing the same instance across threads without proper synchronization can lead to race conditions, incorrect values, or even deadlocks if improperly managed.
Using a Seed for Predictable Random Numbers
Tim then demonstrates the use of a seed. A seed is a starting value that determines the sequence of numbers generated. For example, using a seed of 25 for both RNG1 and RNG2:
Random rng1 = new(25);
Random rng2 = new(25);Both instances produce the same sequence of numbers. Tim emphasizes that this is a feature, not a bug, and is useful for scenarios where reproducibility is required. This can help in unit testing or debugging a multi-threaded program where you need to reproduce a sequence of random values across two or more threads.
Thread-Safe Random Numbers with Random.Shared
Tim introduces Random.Shared, a modern, thread-safe way to generate random numbers. Unlike creating instances manually, Random.Shared is a static, shared resource that handles concurrent access automatically:
int output1 = Random.Shared.Next();
int output2 = Random.Shared.Next();With this approach, you don’t need to worry about locks, static constructors, or shared resources. The thread-safe collections built into .NET and the Random.Shared instance make it safe to use multiple threads concurrently.
Advantages of Random.Shared
Tim explains several benefits of this approach:
No Instantiation Required: You don’t need separate instances for each thread.
Thread-Safe by Default: Safe to use in parallel tasks or worker threads.
- Simple API: Supports integers, decimals, and even array shuffling.
He does note one limitation: you cannot set a seed, meaning sequences are not predictable. For scenarios where the same order of numbers is needed across multiple runs, you should still use individual instances with a seed.
Ensuring Thread Safety in Complex Code
While Random.Shared handles thread safety internally, Tim’s examples allow us to discuss general thread safety techniques in C#. In more complex code or multi-threaded environments, you often need to synchronize correctly to avoid race conditions and ensure data integrity. Some common techniques include:
Lock Statement: Prevents multiple threads from accessing a critical section simultaneously.
Static Members and Static Constructors: Can be used to initialize shared resources safely.
Thread-Safe Collections: Classes like ConcurrentQueue, ConcurrentStack, and ConcurrentDictionary allow safe concurrent access.
- Mutual Exclusion (Mutex/Monitor): Ensures only one thread can execute a section of code at a time.
Tim explains that in most cases for random numbers, Random.Shared removes the need for these techniques, but in multi-threaded applications with shared resources, these methods are essential to avoid deadlocks and unexpected values.
Practical Guidelines from Tim Corey
Tim provides clear guidance for using random numbers safely in multi-threaded applications:
Use Random.Shared for simplicity: Ideal when you just need a random value and performance matters.
Use seeded instances for reproducibility: Necessary when tracking the same data or debugging in multi-threaded environments.
Avoid Random in cryptography: For security, use cryptographic libraries instead.
- Understand thread safety basics: Know when your code is accessed by multiple threads and implement synchronization only when necessary.
He reassures developers that Random.Shared works even when millions of calls per millisecond are made from multiple threads, and it handles concurrent access efficiently.
Conclusion: Safe and Simple Random Numbers in C#
Tim Corey’s video demonstrates that generating thread-safe random numbers in C# is easier than many developers realize. Using Random.Shared, you can avoid many pitfalls associated with multi-threaded environments. For cases where predictable sequences are necessary, using individual instances with a seed provides a reliable approach.
Understanding thread safety, critical sections, and synchronization techniques ensures your code remains correct and performs well in multi-threaded applications. Following Tim’s guidance, developers can confidently write code thread safe for worker threads, async methods, and parallel tasks without worrying about race conditions, deadlocks, or unexpected values.

