如何在 class 线程中使 属性 安全
How to make a property in a class thread safe
我有一个具有某些功能的控制台应用程序,我有一个 class 执行一些操作,例如向服务发布一些消息。现在,在 class 上发布的每条消息上,我正在增加一个 counter.And 我有一个与该计数器关联的委托,因此在达到限制后,我在 Program.cs 上有一个事件要阻止call.Now 我正在异步执行发布部分。那么如何保护计数器属性.
下面是我的代码:
Program.cs
var objMessageManager = new MessageManager();
objMessageManager.MaximumLimitToStopRequest += objMessageManager_MaximumLimitToStopRequest;
static void objMessageManager_MaximumLimitToStopRequest(object sender, int ItemCount)
{
if (ItemCount > Settings.MessageLimit)
{
Task.Delay(Settings.MessageDelayTime);
}
}
下面是 MessageManager.cs class
的代码
internal delegate void StopRequestEventHandler(object sender, int ProcessedItem);
public event StopRequestEventHandler MaximumLimitToStopRequest;
private int ProcessedItem;
private int noOfProcessed;
public int NoOfProcessed
{
get
{
return ProcessedItem;
}
private set
{
ProcessedItem = value;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, ProcessedItem);
}
}
}
这是方法中的代码
internal async void CreateMessage(xyz sMessageObj)
{
try
{
await Task.Run(() =>
{
// Code to publish the message
});
Interlocked.Increment(ref noOfProcessed);
NoOfProcessed = noOfProcessed;
}
catch (Exception ex)
{
Log.Error("Exception occurred .", ex);
}
}
请忽略变量和所有的命名错误。该应用程序 运行 很好。我需要帮助使其在读取和 writing/incrementing NoOfProcessed 属性.
时都是线程安全的
不,您的代码在任何情况下都不是线程安全的。首先,在 StopRequestEventHandler
class 中,您没有将 ProcessedItem
或 noOfProcessed
标记为 volatile
。第二,Interlocked.Increment
不行,还得用Interlocked
、CompareExchange
的CAS运算。第三,您应该考虑创建一个 queue
,因为 ProcessedItem
属性 可以作为 race-condition
.
的目标
A queue
最简单的形式是更新值的简单 Array
,所以我们有一个数组,currentPosition
在上面。在 CAS-Exchange
operation 之后,您只需独立于其他线程使用基于索引的数组项。所以代码将是这样的(这是一个非常直接的实现,你应该尝试编写自己的代码):
int[] processedItems = new int[INITIAL_SIZE];
int currentItem = 0;
var current = currentItem;
// make first attempt...
if (Interlocked.CompareExchange(ref currentItem, current + 1, current) != current)
{
// if we fail, go into a spin wait, spin, and try again until succeed
var spinner = new SpinWait();
do
{
spinner.SpinOnce();
current = currentItem;
}
while (Interlocked.CompareExchange(ref currentItem, current + 1, current) != current);
}
// store the value in array
processedItems[current] = value
老实说,我对你的代码很困惑。
你有
private int ProcessedItem;
private int noOfProcessed;
然后你的属性(名字也很奇怪)是
public int NoOfProcessed
{
get
{
return ProcessedItem;
}
private set
{
ProcessedItem = value;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, ProcessedItem);
}
}
}
但是你在逻辑中递增 noOfProcessed
(Interlocked.Increment(ref noOfProcessed);
我建议像这样简单地重写您的代码:
private int noOfProcessed;
...
...
Interlocked.Increment(ref noOfProcessed);
if (MaximumLimitToStopRequest != null) // Warning: this is NOT thread safe
{
MaximumLimitToStopRequest(this, noOfProcessed);
}
...
public int NoOfProcessed
{
get
{
return noOfProcessed;
}
}
或者像这样(这会更慢但更安全,因为您也以线程安全的方式包装事件调用)
lock(_syncLock)
{
++noOfProcessed;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, noOfProcessed);
}
}
虽然我非常喜欢 VMAtm 的队列想法。线程安全队列似乎更适合您的任务,但只要您在单个应用程序域中操作,上述解决方案(特别是 lock
解决方案)就应该是线程安全的,而且这是唯一的地方事件被调用。
您可以使用 ReaderWriterLockSlim。
它是一个锁,可以让您保持线程安全并保持一点性能。
使用
try
{
// lock you code in write to change the value and read to just read it.
}
finally
{
// release the lock
}
锁 (ReaderWriterLockSlim) 让 reader 仅在请求写入锁时通过并锁定。
我有一个具有某些功能的控制台应用程序,我有一个 class 执行一些操作,例如向服务发布一些消息。现在,在 class 上发布的每条消息上,我正在增加一个 counter.And 我有一个与该计数器关联的委托,因此在达到限制后,我在 Program.cs 上有一个事件要阻止call.Now 我正在异步执行发布部分。那么如何保护计数器属性.
下面是我的代码:
Program.cs
var objMessageManager = new MessageManager();
objMessageManager.MaximumLimitToStopRequest += objMessageManager_MaximumLimitToStopRequest;
static void objMessageManager_MaximumLimitToStopRequest(object sender, int ItemCount)
{
if (ItemCount > Settings.MessageLimit)
{
Task.Delay(Settings.MessageDelayTime);
}
}
下面是 MessageManager.cs class
的代码internal delegate void StopRequestEventHandler(object sender, int ProcessedItem);
public event StopRequestEventHandler MaximumLimitToStopRequest;
private int ProcessedItem;
private int noOfProcessed;
public int NoOfProcessed
{
get
{
return ProcessedItem;
}
private set
{
ProcessedItem = value;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, ProcessedItem);
}
}
}
这是方法中的代码
internal async void CreateMessage(xyz sMessageObj)
{
try
{
await Task.Run(() =>
{
// Code to publish the message
});
Interlocked.Increment(ref noOfProcessed);
NoOfProcessed = noOfProcessed;
}
catch (Exception ex)
{
Log.Error("Exception occurred .", ex);
}
}
请忽略变量和所有的命名错误。该应用程序 运行 很好。我需要帮助使其在读取和 writing/incrementing NoOfProcessed 属性.
时都是线程安全的不,您的代码在任何情况下都不是线程安全的。首先,在 StopRequestEventHandler
class 中,您没有将 ProcessedItem
或 noOfProcessed
标记为 volatile
。第二,Interlocked.Increment
不行,还得用Interlocked
、CompareExchange
的CAS运算。第三,您应该考虑创建一个 queue
,因为 ProcessedItem
属性 可以作为 race-condition
.
A queue
最简单的形式是更新值的简单 Array
,所以我们有一个数组,currentPosition
在上面。在 CAS-Exchange
operation 之后,您只需独立于其他线程使用基于索引的数组项。所以代码将是这样的(这是一个非常直接的实现,你应该尝试编写自己的代码):
int[] processedItems = new int[INITIAL_SIZE];
int currentItem = 0;
var current = currentItem;
// make first attempt...
if (Interlocked.CompareExchange(ref currentItem, current + 1, current) != current)
{
// if we fail, go into a spin wait, spin, and try again until succeed
var spinner = new SpinWait();
do
{
spinner.SpinOnce();
current = currentItem;
}
while (Interlocked.CompareExchange(ref currentItem, current + 1, current) != current);
}
// store the value in array
processedItems[current] = value
老实说,我对你的代码很困惑。
你有
private int ProcessedItem;
private int noOfProcessed;
然后你的属性(名字也很奇怪)是
public int NoOfProcessed
{
get
{
return ProcessedItem;
}
private set
{
ProcessedItem = value;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, ProcessedItem);
}
}
}
但是你在逻辑中递增 noOfProcessed
(Interlocked.Increment(ref noOfProcessed);
我建议像这样简单地重写您的代码:
private int noOfProcessed;
...
...
Interlocked.Increment(ref noOfProcessed);
if (MaximumLimitToStopRequest != null) // Warning: this is NOT thread safe
{
MaximumLimitToStopRequest(this, noOfProcessed);
}
...
public int NoOfProcessed
{
get
{
return noOfProcessed;
}
}
或者像这样(这会更慢但更安全,因为您也以线程安全的方式包装事件调用)
lock(_syncLock)
{
++noOfProcessed;
if (MaximumLimitToStopRequest != null)
{
MaximumLimitToStopRequest(this, noOfProcessed);
}
}
虽然我非常喜欢 VMAtm 的队列想法。线程安全队列似乎更适合您的任务,但只要您在单个应用程序域中操作,上述解决方案(特别是 lock
解决方案)就应该是线程安全的,而且这是唯一的地方事件被调用。
您可以使用 ReaderWriterLockSlim。
它是一个锁,可以让您保持线程安全并保持一点性能。
使用
try
{
// lock you code in write to change the value and read to just read it.
}
finally
{
// release the lock
}
锁 (ReaderWriterLockSlim) 让 reader 仅在请求写入锁时通过并锁定。