C#4.0:是否有现成的,线程安全的自动实现属性?

时间:2010-11-29 13:42:34

标签: c# .net multithreading thread-safety properties

我想对自动实现的属性进行线程安全的读写访问。我在C#/ .NET框架中缺少这个功能,即使在它的最新版本中也是如此。 充其量,我希望像

这样的东西
[Threadsafe]
public int? MyProperty { get; set; }

我知道有很多代码示例可以实现这一点,但是我只想确保在使用.NET框架方法之前仍然无法实现这一点,而在我自己实现之前。我错了吗?

编辑:正如一些答案详细阐述了原子性,我想说明我只想知道它:只要(并且不超过)一个线程正在读取属性的值,没有其他线程可以更改该值。因此,多线程不会引入无效值。我选择了int?打字,因为这是我目前关注的问题。

EDIT2:I have found the specific answer to the example with Nullable here, by Eric Lippert

4 个答案:

答案 0 :(得分:16)

正确;没有这样的设备。大概是你试图防止读取该字段,而另一个线程已经改变了它的一半(原子性)?请注意,许多(小)基元从类型的线程问题本质上是安全的:

  

5.5变量引用的原子性

     

读取和写入以下数据   类型是原子的:boolcharbyte,   sbyteshortushortuintint,   float引用类型。在   另外,读取和写入枚举   具有基础类型的类型   以前的清单也是原子的。

但是说实话,这只是穿越冰山的一角; 本身通常仅仅具有线程安全属性是不够的;大多数情况下,同步块的范围必须更多而不仅仅是一次读/写。

还有这么多不同的方法可以使线程安全,具体取决于访问配置文件;

  • lock
  • ReaderWriterLockSlim
  • 引用 - 交换到某个类(基本上是Box<T>,因此在这种情况下为Box<int?>
  • Interlocked(所有伪装)
  • volatile(在某些情况下;它不是魔杖......)

(更不用说让它变成不可变的(通过代码,或只是选择不改变它,这通常是最简单的方式使其成为线程安全的)

答案 1 :(得分:7)

我在这里回答要添加Marc的答案,他说“根据访问配置文件,还有很多不同的方法来制作线程安全的东西”。

我只是想补充一点,原因之一就是有很多方式是线程安全的,当我们说某些东西是线程安全的时候,我们有明确提供的安全性。

对于几乎任何可变对象,都会有一些方法来处理它不是线程安全的(注意几乎任何,异常即将出现)。考虑一个具有以下(线程安全)成员的线程安全队列;入队行动,出列行动和计数财产。通过内部锁定每个成员或甚至使用无锁技术来构建其中一个相对容易。

然而,假设我们使用了这样的对象:

if(queue.Count != 0)
    return queue.Dequeue();

上面的代码不是线程安全的,因为无法保证在(线程安全)Count返回1后,另一个线程不会出列,从而导致第二个操作失败。

它在很多方面仍然是一个线程安全的对象,特别是即使在这种失败的情况下,失败的出队操作也不会使对象进入无效状态。

为了使对象在任何给定的操作组合中成为一个完整的线程安全的,我们必须使它在逻辑上不可变(它可能具有内部可变性,线程安全操作将内部状态更新为优化 - 例如通过记忆或根据需要从数据源加载,但外部必须看起来是不可变的)或严重减少可能的外部操作数量(我们可以创建一个只有Enqueue和{{1的线程安全队列它始终是线程安全的,但它既减少了可能的操作,又强制将失败的dequeue重新定义为不是失败,并强制改变逻辑来调用我们之前版本的代码。

其他任何事情都是部分保证。我们免费得到一些部分保证(正如Marc所说,对于某些自动属性而言,就单独原子而言已经是线程安全的 - 在某些情况下我们需要的是所有线程安全,但在其他情况下不会去任何地方附近很远)。

让我们考虑一个属性,将这个部分保证添加到我们还没有得到它的情况。它对我们有多大的价值?嗯,在某些情况下它会是完美的,但在其他情况下它不会。在出列之前回到我们的测试案例,在TryDequeue上有这样的保证并没有多大用处 - 我们有这样的保证,而且代码在多线程条件下仍然以一种不单一的方式失败 - 螺纹条件。

更重要的是,将此保证添加到尚未拥有它的案例中至少需要一定程度的开销。一直担心开销可能是过早的优化,但增加开销以获得没有收益是过早的悲观,所以不要这样做!更重要的是,如果我们提供更广泛的并发控制以使一组操作真正是线程安全的,那么我们将使更窄的并发控制变得无关紧要,并且它们变得纯粹的开销 - 所以我们甚至没有从我们的在某些情况下开销;它几乎总是纯粹浪费。

并不清楚并发问题的范围有多宽或多窄。我们是否只需要锁定(或类似)该属性,还是需要锁定所有属性?我们是否还需要锁定非自动操作,甚至是可能的?

这里没有一个好的单一答案(在编写自己的解决方案时,它们可能是难以回答的问题,不要介意在其他人使用此[Threadsafe]属性时会产生此类代码的代码中回答它)

此外,任何给定的方法都会有一组不同的条件,其中可能发生死锁,活锁和类似问题,因此我们可以通过将线程安全视为我们可以盲目地应用于属性的事实来减少线程安全性

没有能够找到这些问题的单一通用答案,没有提供单一通用实现的好方法,任何此类[Threadsafe]属性最多都是非常有限的价值。最后,在使用它的程序员的心理层面上,很可能导致他们创建了一个线程安全的类,而事实上他们没有;这会使它实际上比无用更糟糕。

答案 2 :(得分:1)

不,不可能。这里没有免费午餐。你的autoproperties甚至需要更多的提示(线程安全,INotifyPropertyChanged),这是你手动做的事情 - 没有自动属性魔术。

答案 3 :(得分:-1)

根据C#4.0规范,这种行为没有改变:

第10.7.3节自动实施的属性

当属性被指定为自动实现的属性时,隐藏的后备字段将自动可用于该属性,并且实现访问器以读取和写入该后备字段。

以下示例:

 public class Point {
    public int X { get; set; } // automatically implemented
    public int Y { get; set; } // automatically implemented
}

等同于以下声明:

public class Point {
    private int x;
    private int y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}