在特定情况,我们希望这样一个场景:
N个线程同时调用同一个类实例的同一个操作方法,并且同一个变量可以面向每一个线程存储独立的值。比如,某变量X,它对于线程A的值与对于线程B的值是相互独立的。线程A设置了X的值为3,那么只要代码是在线程A上执行的,那么变量X的值就是3;线程B设置X值为7,那么在线程B的代码中X的值就为7。
同样一个X变量,不同的线程访问它就会读写不同的值 。
有些时候,我们需要以上功能。只要把希望基于线程本地所使用的值的变量类型声明为ThreadLocal<T>类型即可,其中T表示该变量中要存储的值的数据类型。
下面定义一个类:
public sealed class ThreadingWork { private Random m_rand = null; private ThreadLocal<int> m_localVal = default(ThreadLocal<int>); public ThreadingWork() { m_rand = new Random(); m_localVal = new ThreadLocal<int>(); } private void MakeRandom() { m_localVal.Value = m_rand.Next(0, 1000); } public void RunOnThreads() { // 为变量生成值 MakeRandom(); // 引发事件 string str = $"在线程{Thread.CurrentThread.ManagedThreadId}上设置的值为:{m_localVal.Value}"; ThreadingRuned?.Invoke(this, new RunThreadEventArgs(str)); } public event EventHandler<RunThreadEventArgs> ThreadingRuned; } /// <summary> /// 自定义事件参数类 /// </summary> public class RunThreadEventArgs : EventArgs { internal RunThreadEventArgs(string s) { Value = s; } public string Value { get; private set; } }
m_localVal变量是类的字段,它里面存放的是int类型的值。RunOnThreads方法会被不同的线程调用,线程执行方法后,会生成一个随机整数,并存到m_localVal变量中。接着引发ThreadingRuned事件,并将m_localVal变量中存放的值随着事件传递,以便被其他代码使用。
下面,我们测试一下。
// 实例化对象 ThreadingWork work = new ThreadingWork(); // 附加事件处理 work.ThreadingRuned += Work_ThreadingRuned; // 创建30个Task来干活 for(int n = 0; n < 30; n++) { Thread t = new Thread(work.RunOnThreads); t.Start(); }
上面代码创建了30个线程,并且线程所执行的都是同一个实例work上的RunOnThreads方法。
当代码运行后,见证奇迹的一刻到了。
从运行结果中可以发现,同一个实例方法被多个线程调用,但m_localVal变量可以存放来自各个线程的值,而每个线程所读取的都是基于当前线程的值,即每个线程读写的值都不同。
尤其是在实现多线程下载的案例中,可以使用同一个实例变量来记录源自不同线程的下载进度(假设每个线程开启一个下载任务)。
例子代码下载。