如何使类中的属性线程安全

本文关键字:线程 安全 属性 何使类 | 更新日期: 2023-09-27 18:09:03

我有一个控制台应用程序,它有一些功能,我有一个类做一些操作,比如向服务发布一些消息。现在,在该类上发布的每条消息上,我都会增加一个计数器。我有一个与那个计数器相关联的委托,所以在达到限制后,我在program。cs上有一个事件来阻止调用。现在我正在做异步发布部分。那么如何保护counter属性呢?

下面是我的代码:

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类的代码
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);
    }
}

请忽略变量和所有的命名错误。应用程序运行良好。我需要帮助,使其线程安全的读取和写入/递增NoOfProcessed属性

如何使类中的属性线程安全

不,你的代码在任何情况下都不是线程安全的。首先,在StopRequestEventHandler中,你没有把ProcessedItemnoOfProcessed标记为volatile。其次,Interlocked.Increment是不够的,你必须使用Interlocked, CompareExchange的cas操作。第三,您应该考虑创建queue,因为ProcessedItem属性可以作为race-condition的目标。

最简单的queue形式是更新值的简单Array,所以我们有一个数组,currentPosition在它上面。在CAS- Exchange操作之后,您只需独立于其他线程使用基于索引的数组项。代码将是这样的(这是一个非常直接的实现,你应该试着自己写代码):

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)允许读通过,只有当写锁是ask时才锁。