建议22:确保集合的线程安全
集合线程安全是指多个线程上添加或删除元素时,线程键必须保持同步。
下面代码模拟了一个线程在迭代过程中,另一个线程对元素进行了删除。
classPRogram
{staticList<Person> list =newList<Person>()
{newPerson() { Name ="Rose", Age =19},newPerson() { Name ="Steve", Age =45},newPerson() { Name ="Jessica", Age =20},
};staticAutoResetEvent autoSet =newAutoResetEvent(false);staticvoidMain(string[] args)
{
Thread t1=newThread(() =>{//确保等待t2开始之后才运行下面的代码autoSet.WaitOne();foreach(variteminlist)
{
Console.WriteLine("t1:"+item.Name);
Thread.Sleep(1000);
}
});
t1.Start();
Thread t2=newThread(() =>{//通知t1可以执行代码autoSet.Set();//沉睡1秒是为了确保删除操作在t1的迭代过程中Thread.Sleep(1000);
list.RemoveAt(2);
});
t2.Start();
}
}classPerson
{publicstringName {get;set; }publicintAge {get;set; }
}
以上代码运行过程会抛出InvalidOperationException:“集合已修改,可能无法执行枚举。”
早在泛型集合出现之前,非泛型集合一般提供一个SyncRoot属性,要保证非泛型集合的线程安全,可以通过锁定该属性来实现。如果上面的集合用ArrayList代替,保证其线程安全则应该在迭代和删除的时候都加上lock,代码如下:
staticArrayList list =newArrayList()
{newPerson() { Name ="Rose", Age =19},newPerson() { Name ="Steve", Age =45},newPerson() { Name ="Jessica", Age =20},
};staticAutoResetEvent autoSet =newAutoResetEvent(false);staticvoidMain(string[] args)
{
Thread t1=newThread(() =>{//确保等待t2开始之后才运行下面的代码autoSet.WaitOne();lock(list.SyncRoot)
{foreach(Person iteminlist)
{
Console.WriteLine("t1:"+item.Name);
Thread.Sleep(1000);
}
}
});
t1.Start();
Thread t2=newThread(() =>{//通知t1可以执行代码autoSet.Set();//沉睡1秒是为了确保删除操作在t1的迭代过程中Thread.Sleep(1000);lock(list.SyncRoot)
{
list.RemoveAt(2);
Console.WriteLine("删除成功");
}
});
t2.Start();
}
以上代码不会抛出异常,因为锁定通过互斥的机制保证了同一时刻只能有一个线程操作集合元素。我们进而发现泛型集合没有这样的属性,必须要自己创建一个锁定对象来完成同步任务。可以通过new一个静态对象来进行锁定,代码如下:
staticList<Person> list =newList<Person>()
{newPerson() { Name ="Rose", Age =19},newPerson() { Name ="Steve", Age =45},newPerson() { Name ="Jessica", Age =20},
};staticAutoResetEvent autoSet =newAutoResetEvent(false);staticobjectsycObj =newobject();staticvoidMain(string[] args)
{//object sycObj = new object();Thread t1 =newThread(() =>{//确保等待t2开始之后才运行下面的代码autoSet.WaitOne();lock(sycObj)
{foreach(Person iteminlist)
{
Console.WriteLine("t1:"+item.Name);
Thread.Sleep(1000);
}
}
});
t1.Start();
Thread t2=newThread(() =>{//通知t1可以执行代码autoSet.Set();//沉睡1秒是为了确保删除操作在t1的迭代过程中Thread.Sleep(1000);lock(sycObj)
{
list.RemoveAt(2);
Console.WriteLine("删除成功");
}
});
t2.Start();
}
转自:《编写高质量代码改善C#程序的157个建议》陆敏技