我想知道以下代码是否是线程安全的,我认为它不是。我怎么可能让它安全线程?
基本上我有一个ConcurrentDictionary
,它充当数据库表的缓存。我想每10秒查询一次数据库并更新数据库缓存。将有其他线程一直查询这本字典。
我不能只使用TryAdd
因为我可能还删除了一些元素。所以我决定不搜索整个字典来更新,添加或删除。我只想重新初始化字典。请告诉我这是否是一个愚蠢的想法。
我担心的是,当我重新初始化字典时,在初始化发生时查询线程将不再通过线程安全的实例。出于这个原因,我在更新时使用了字典锁,但是我不确定这是否正确,因为锁中的对象发生了变化?
private static System.Timers.Timer updateTimer;
private static volatile Boolean _isBusyUpdating = false;
private static ConcurrentDictionary<int, string> _contactIdNames;
public Constructor()
{
// Setup Timers for data updater
updateTimer = new System.Timers.Timer();
updateTimer.Interval = new TimeSpan(0, 0, 10, 0).TotalMilliseconds;
updateTimer.Elapsed += OnTimedEvent;
// Start the timer
updateTimer.Enabled = true;
}
private void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
if (!_isBusyUpdating)
{
_isBusyUpdating = true;
// Get new data values and update the list
try
{
var tmp = new ConcurrentDictionary<int, string>();
using (var db = new DBEntities())
{
foreach (var item in db.ContactIDs.Select(x => new { x.Qualifier, x.AlarmCode, x.Description }).AsEnumerable())
{
int key = (item.Qualifier * 1000) + item.AlarmCode;
tmp.TryAdd(key, item.Description);
}
}
if (_contactIdNames == null)
{
_contactIdNames = tmp;
}
else
{
lock (_contactIdNames)
{
_contactIdNames = tmp;
}
}
}
catch (Exception e)
{
Debug.WriteLine("Error occurred in update ContactId db store", e);
}
_isBusyUpdating = false;
}
}
/// Use the dictionary from another Thread
public int GetIdFromClientString(string Name)
{
try
{
int pk;
if (_contactIdNames.TryGetValue(Name, out pk))
{
return pk;
}
}
catch { }
//If all else fails return -1
return -1;
}
答案 0 :(得分:2)
你似乎为自己做了很多工作。以下是我将如何解决这个问题:
public class Constructor
{
private volatile Dictionary<int, string> _contactIdNames;
public Constructor()
{
Observable
.Interval(TimeSpan.FromSeconds(10.0))
.StartWith(-1)
.Select(n =>
{
using (var db = new DBEntities())
{
return db.ContactIDs.ToDictionary(
x => x.Qualifier * 1000 + x.AlarmCode,
x => x.Description);
}
})
.Subscribe(x => _contactIdNames = x);
}
public string TryGetValue(int key)
{
string value = null;
_contactIdNames.TryGetValue(key, out value);
return value;
}
}
我正在使用Microsoft的Reactive Extensions(Rx)框架 - NuGet“Rx-Main” - 用于更新字典的计时器。
Rx应该相当简单。如果您之前没有以非常简单的术语看过它,就像LINQ遇到事件一样。
如果您不喜欢Rx,那么请使用您当前的计时器型号。
所有这些代码都是每隔10秒从数据库创建一个新字典。我只是使用普通字典,因为它只是从一个线程创建的。由于引用赋值是原子的,因此您可以在需要时使用完整的线程安全性重新分配字典。
只要元素不变,多个线程就可以安全地从字典中读取。
答案 1 :(得分:1)
你的代码不是线程安全的。
_isBusyUpdating
变量。_contactIdNames
,而不仅仅是null
。此代码与singleton pattern类似,初始化时也存在同样的问题。您可以使用Double checked locking解决此问题。但是,访问条目时还需要双重检查锁定。
如果您一次更新整个字典,则每次访问时都需要锁定当前值。否则,您可以在它仍在更改时访问它并获得错误。因此,您需要每次锁定变量或使用Interlocked
。
由于MSDN says volatile
应该使用_isBusyUpdating
来处理,它应该是线程安全的。
如果您不想跟踪_contactIdNames
线程安全性,请尝试在同一个字典中实现每个条目的更新。问题在于DB和当前值之间的差异检测(已删除或添加了哪些条目,其他条目可以简单重写),但不是线程安全,因为ConcurrentDictionary
已经是线程安全的。
答案 2 :(得分:0)
我想知道以下代码是否是线程安全的,我假设它 不是。我怎么可能让它安全线程?
我相信不是。首先,我为ConcurrentDictionary
创建属性并检查get
方法内是否正在进行更新,如果是,我将返回先前版本的对象:
private object obj = new object();
private ConcurrentDictionary<int, string> _contactIdNames;
private ConcurrentDictionary<int, string> _contactIdNamesOld;
private volatile bool _isBusyUpdating = false;
public ConcurrentDictionary<int, string> ContactIdNames
{
get
{
if (!_isBusyUpdating) return _contactIdNames;
return _contactIdNamesOld;
}
private set
{
if(_isBusyUpdating) _contactIdNamesOld =
new ConcurrentDictionary<int, string>(_contactIdNames);
_contactIdNames = value;
}
}
你的方法可以是:
private static void OnTimedEvent(Object source, System.Timers.ElapsedEventArgs e)
{
if (_isBusyUpdating) return;
lock (obj)
{
_isBusyUpdating = true;
// Get new data values and update the list
try
{
ContactIdNames = new ConcurrentDictionary<int, string>();
using (var db = new DBEntities())
{
foreach (var item in db.ContactIDs.Select(x => new { x.Qualifier, x.AlarmCode, x.Description }).AsEnumerable())
{
int key = (item.Qualifier * 1000) + item.AlarmCode;
_contactIdNames.TryAdd(key, item.Description);
}
}
}
catch (Exception e)
{
Debug.WriteLine("Error occurred in update ContactId db store", e);
_contactIdNames = _contactIdNamesOld;
}
finally
{
_isBusyUpdating = false;
}
}
}
P.S。
我担心的是,当我重新初始化字典时,查询 线程不会因为线程安全而无法实例化 初始化发生。出于这个原因,我使用了锁 更新时的字典,但我不确定这是否正确 当对象在锁中发生变化?
ConcurrentDictionary<T>
类型是线程安全的而不是它的实例,所以即使你创建新实例并更改它的引用 - 也不用担心。