Passing Parameters to Managed Threads
In the initial design of the .NET Thread class, the ThreadStart delegate was limited to methods with no parameters. However, real-world applications frequently require passing data—such as configuration settings, file paths, or calculation inputs—into a background thread.
There are two primary ways to achieve this: using the modern Lambda Expression approach or the legacy ParameterizedThreadStart delegate.
The Modern Approach: Lambda Expressions
The most flexible and readable way to pass data into a thread is through a lambda expression. This technique effectively "captures" variables from the surrounding scope and passes them into the target method.
One of the greatest advantages of this method is that it allows for strong typing. You aren't limited to a single object; you can pass multiple arguments of any type directly.
Code Example: Passing Multiple Arguments
In this example, we will pass both a string and an integer to a worker method.
The Legacy Approach: ParameterizedThreadStart
Before lambdas were common, .NET utilized the ParameterizedThreadStart delegate. This delegate accepts a single parameter of type object. While functional, it requires boxing (converting value types to objects) and explicit casting inside the target method, which can lead to runtime errors if the type is incorrect.
Code Example: Using the Legacy Delegate
Key Considerations
When passing parameters to threads, keep the following best practices in mind:
- Closure Issues: If you pass a variable that changes (like a loop counter), the thread might see the final value of the variable rather than the value it had when the thread was started. Always create a local copy of the variable before passing it into a lambda.
- Thread Safety: If you pass a reference to an object (like a List or a custom Class), both the main thread and the worker thread will have access to it. If both try to modify it simultaneously, it will cause data corruption.
- Scalability: While passing parameters this way is powerful, creating a new
Threadobject for every small task is resource-intensive. For high-volume tasks, consider using theTaskclass or theThreadPool.