这段代码实际上是线程安全的吗?

本文关键字:安全 线程 实际上 段代码 代码 | 更新日期: 2023-09-27 18:02:15

我有一个关于以下代码段的问题,我在微软的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);

这段代码实际上是线程安全的吗?

这段代码实际上是线程安全的吗?

不,它不是。

根据文档,ArrayList实例不支持并发修改,除非它由Synchronized方法返回,但这里不是这样。

虽然它可能不明显,但并发修改可能发生在您的示例中。Task排在ThreadPool后面,它将由这个池中的一些线程运行。如果用户双击btnCreateStudent,将创建两个任务,并且由于Thread.Sleep不是非常精确,并且无论如何,任务不必立即执行(例如ThreadPool队列可能已满),因此两个任务虽然在不同的时间调度,但可能同时执行。

使代码线程安全的最好方法是什么?

这取决于你对"最好的"的定义。

第一个解决方案是使用Synchronized方法创建ArrayList

private ArrayList students = ArrayList.Synchronized(new ArrayList());

但是您仍然需要使用锁来枚举这个列表。

枚举一个集合本质上不是线程安全的过程。即使一个集合是同步的,其他线程也可以仍然修改集合,这将导致枚举数抛出例外。为了保证枚举期间的线程安全,可以在整个枚举过程中锁定集合,或者捕获由其他线程所做的更改导致的异常。

另一种解决方案是使用List<T>并在访问集合的任何地方添加锁。List<T>优于ArrayList,因为它包含元素类型,所以您不必在读取时强制转换它们,或者您不会意外地将不兼容的类型添加到集合中。

如果你不关心项目的顺序,那么你应该使用ConcurrentBag<T>,它不需要任何锁。

"线程安全性"在很大程度上取决于您在单独线程之外所做的工作。如果在任务运行期间,在任务之外根本不触及students,则代码是线程安全的。

如果您在任务的生存期内在外部使用students,您应该同步访问。您可以使用lock或其他同步方法来完成此操作。

你当然也可以使用一些并发集合