我对在Microsoft的C#教程网页上找到的以下代码段有疑问。在代码中,它们提供了任务演示。在事件处理程序中,他们创建了一个更新未受保护的集合的任务。
这段代码是否是线程安全的?在我看来,它不是。使这段代码线程安全的最佳方法是什么?
private ArrayList students = new ArrayList();
private void btnCreateStudent_Click(object sender, RoutedEventArgs e)
{
Student newStudent = new Student();
newStudent.FirstName = txtFirstName.Text;
newStudent.LastName = txtLastName.Text;
newStudent.City = txtCity.Text;
ClearForm();
Task task1 = new Task(() => AddToCollection(newStudent));
task1.Start();
ClearForm();
}
private void AddToCollection(Student student)
{
Thread.Sleep(5000);
students.Add(student);
int count = students.Count;
MessageBox.Show("Student created successfully. Collection contains " + count.ToString() + " Student(s).");
}
我不同意以下陈述
students.Add(student);
我认为应该用锁来保护它。
答案 0 :(得分:2)
这段代码实际上是线程安全的吗?
不,不是。
根据documentation,ArrayList
实例不支持并发修改,除非它由Synchronized
方法返回,并且在这里不是这样。
虽然可能并不明显,但在您的示例中可能会发生并发修改。 Task
排队到ThreadPool
,它将由此池中的某个线程运行。如果用户双击btnCreateStudent
将创建两个任务,并且由于Thread.Sleep
不是非常精确,无论如何,不必立即执行任务(例如,ThreadPool队列可能已满)因此,虽然两个任务在不同时间安排,但可以同时执行。
使此代码线程安全的最佳方法是什么?
这取决于你所说的“最好的”。
第一个解决方案是使用Synchronized
方法创建ArrayList
。
private ArrayList students = ArrayList.Synchronized(new ArrayList());
但你仍然需要使用锁来枚举这个列表。
通过集合枚举本质上不是线程安全的 程序。即使集合是同步的,其他线程也可以 仍然修改集合,这会导致枚举器抛出一个 例外。为了在枚举期间保证线程安全,您可以 要么在整个枚举过程中锁定集合,要么抓住它 由其他线程所做的更改导致的异常。
另一种解决方案是使用List<T>
并在访问集合的任何地方添加锁。 List<T>
优于ArrayList
,因为它包含元素类型,因此您不必在读取时强制转换它们,或者您不会意外地将不兼容的类型添加到集合中。
如果您不关心项目的顺序,那么您应该使用ConcurrentBag<T>
,这不需要任何锁定。
答案 1 :(得分:1)
“线程安全性”在很大程度上取决于您在单独线程之外执行的操作。
如果在任务运行期间没有完成任务之外的students
,则代码是线程安全的。
如果您在任务的生命周期内使用students
,则应同步访问权限。
您可以使用lock
或other synchronization methods。
您当然也可以使用concurrent collections.
中的一些内容