在看Artech的博客时发现他的这篇难道调用ThreadPool.QueueUserWorkItem()的时候,真是必须调用Thread.Sleep(N)吗? 讲到的一个匿名方法造成的问题,在文章后面,有老赵的回复,并且给出了解决方案(查看老赵的“警惕匿名方法造成的变量共享”)。其实不止匿名方法有这个困扰,我们在操作集合的时候,都应该全面考虑到”变量共享“问题。下面就贴一下自己加了几行注释的Artech的源码,从我自己的角度来分析一下:
代码
class Program
{
static void Main(string[] args)
{
List<Action> actions = new List<Action>();
actions.Add(() => Console.WriteLine("A1"));
actions.Add(() => Console.WriteLine("A2"));
actions.Add(() => Console.WriteLine("A3"));
actions.Add(() => Console.WriteLine("A4"));
foreach (var action in actions)
{
//var tmpAction = action; //线程执行这个委托方法就输出正常
//ThreadPool.QueueUserWorkItem(state => tmpAction(), null);
ThreadPool.QueueUserWorkItem(state => action(), null);
//Thread.Sleep(1); //不管有没有这一行 都是有问题的
}
Console.Read();
}
}
{
static void Main(string[] args)
{
List<Action> actions = new List<Action>();
actions.Add(() => Console.WriteLine("A1"));
actions.Add(() => Console.WriteLine("A2"));
actions.Add(() => Console.WriteLine("A3"));
actions.Add(() => Console.WriteLine("A4"));
foreach (var action in actions)
{
//var tmpAction = action; //线程执行这个委托方法就输出正常
//ThreadPool.QueueUserWorkItem(state => tmpAction(), null);
ThreadPool.QueueUserWorkItem(state => action(), null);
//Thread.Sleep(1); //不管有没有这一行 都是有问题的
}
Console.Read();
}
}
运行后,我们看到的结果和我们理想的相差甚远(加上Thread Sleep(1)那一行运行结果有时也不全是我们想要的结果)。
其实我们完全可以这样理解:在foreach循环的时候,action是一个委托方法引用,是引用类型,线程执行的时候,都将执行action变量所在的同一引用地址上的委托方法。而我们将action赋值给一个中间变量tmpAction后,每循环一次,就相当于在内存上重新分配了一段空间,然后线程执行一个新引用地址上的委托方法,这就避免了老赵所说的“匿名方法造成的变量共享”。
ps:我在早前一篇博客里讲到匿名方法的“一个需要注意的地方”的时候也提到了这一点,不知各位是否赞同。