【问题标题】:Dataset in Cache: Collection was modified; enumeration operation might not execute缓存中的数据集:集合已修改;枚举操作可能无法执行
【发布时间】:2011-07-22 21:20:12
【问题描述】:

我将数据集存储在 ASP.Net WebApplication-Cache 中。此 Intranet-app 中的每个用户都使用相同的实例。在插入/更新/删除操作时,数据库将被更新并相应地修改数据集。

但我很少遇到表明我遗漏了什么的异常。我认为它必须与线程安全有关。

 Collection was modified; enumeration operation might not execute

在我访问数据集中的数据表的行中,例如:

Dim view As New DataView(dsERP.ERP_Charge, filter, sort, _
    Data.DataViewRowState.CurrentRows)

当视图枚举数据表时,它显然被另一个线程更改了。

使这个线程安全的最好方法是什么?

编辑:正如您所提到的,我需要在添加/编辑/删除操作时锁定对象。 MSDN* 表示 DataSet 对多个用户来说是线程安全的。这是什么意思,Dataset 中的 DataTables 也是线程安全的吗?以及如何在写操作上锁定单个数据表而不是整个数据集?

*ADO.NET - 多线程编程

ADO.NET 针对性能、吞吐量和可扩展性进行了优化。结果, ADO.NET 对象不锁定资源,只能在单个线程上使用。唯一的那个 例外是 DataSet,它对多个读取器是线程安全的。但是,您需要 在写入期间锁定 DataSet。

这是返回数据集的属性:

Public ReadOnly Property dsERP() As ERPModel.dsERP
    Get
        If Cache("DS_ERP") Is Nothing Then
            Cache("DS_ERP") = New ERPModel.dsERP
            FillDataSet()
        End If
        Return DirectCast(Cache("DS_ERP"), ERPModel.dsERP)
    End Get
End Property

感谢 casperOne,我通过以下方式修改了插入/更新和删除操作(dsRma 是一个数据集):

Dim success As Boolean
SyncLock dsRMA.RMA
     success = insert()
End SyncLock

首先,如果另一个线程尝试枚举 RMA-Table,它现在可以工作吗?其次,锁定更新的数据行而不是锁定整个数据表是否足够(见下文)?

Dim thisRMA As ERPModel.dsRMA.RMARow = dsRMA.RMA.FindByIdRMA(Me.IdRma)
Dim success As Boolean
SyncLock thisRMA
       success = update(thisRMA)
End SyncLock

【问题讨论】:

  • 为什么要缓存数据集?
  • @Lasse:主要是为了避免在任何用户的每次回发时永久访问数据库。但是由于这样的问题,我不再对这个决定很满意。此外,大多数操作是插入和更新,因此无论如何我也必须更新数据库。
  • 通过添加锁,您有效地序列化了对数据集的所有访问,您基本上将您的多用户 Web 应用程序变成了一次一个用户类型的应用程序.这真的是你想要的吗?如果没有,放弃缓存的数据集,与数据库对话。缓存那些不会改变的东西,并为那些改变的东西访问数据库,让它担心锁定什么以及何时锁定。毕竟,这是数据库最擅长的。

标签: .net asp.net multithreading caching ado.net


【解决方案1】:

一般来说,在对序列进行枚举时,您不能执行会修改序列的操作。

因为您将集合存储在 WebApplication 缓存中,所以它可以同时被多个线程命中;您可能有一个线程请求在缓存中输入一个项目,而另一个线程正在尝试对其进行枚举。

为了解决这个问题,您确实应该对数据结构进行线程安全的访问。

您有几个选择。第一个也是最简单的方法是将对序列的访问封装在lock 块中。您必须将添加/编辑/删除操作包装在单个块中,并且您还必须在 整个 序列的枚举中以 lock 块中的相同对象为目标。

这可以很容易地封装在另一个类中,然后您将其存储在缓存中。

如果与写入操作相比,您还执行大量读取操作,那么您可以使用ReaderWriterLockSlim 类来适当地锁定读取和写入;上面的策略将锁定两个并发读取。

另一种解决方案是锁定缓存上的任何添加/编辑/更新操作,当您想要枚举缓存时,锁定并创建序列本身的副本并将其返回;返回序列的副本后,您可以单独枚举该序列,因为该序列与您实际修改的序列不同。

【讨论】:

  • 谢谢。因为我需要一个快速的解决方案,所以我用锁块实现了锁定。你能看看我编辑的问题的下半部分吗?
猜你喜欢
  • 2015-06-17
  • 2020-02-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-17
  • 2013-03-05
  • 2011-10-05
  • 1970-01-01
相关资源
最近更新 更多