63

我正在设计一个我希望在主线程完成配置后使其只读的类,即“冻结”它。Eric Lippert 将这种冰棒称为不变性。冻结后,可以被多个线程并发访问读取。

我的问题是如何以线程安全的方式编写它,这种方式实际上是有效的,即不试图变得不必要的聪明

尝试1:

public class Foobar
{
   private Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }

   public Object ReadSomething()
   {
      return it;
   }
}

Eric Lippert 似乎建议在这篇文章中这样做是可以的。我知道写入具有释放语义,但据我了解,这仅与ordering相关,并不一定意味着所有线程都会在写入后立即看到该值。谁能证实这一点?这意味着这个解决方案不是线程安全的(当然这可能不是唯一的原因)。

尝试2:

以上,但Interlocked.Exchange用于确保实际发布的值:

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (_isFrozen == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

这样做的好处是我们确保发布该值而不会遭受每次读取的开销。如果在写入 _isFrozen 之前没有任何读取被移动,因为 Interlocked 方法使用完整的内存屏障,我猜这是线程安全的。但是,谁知道编译器会做什么(根据 C# 规范的第 3.10 节,这似乎很多),所以我不知道这是否是线程安全的。

尝试 3:

也可以使用Interlocked.

public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

绝对线程安全,但每次读取都必须进行比较交换似乎有点浪费。我知道这种开销可能很小,但我正在寻找一种相当有效的方法(尽管也许就是这样)。

尝试4:

使用volatile

public class Foobar
{
   private volatile Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }
}

但是 Joe Duffy 声明“ sayon​​ara volatile ”,所以我不会认为这是一个解决方案。

尝试 5:

锁定一切,似乎有点矫枉过正:

public class Foobar
{
   private readonly Object _syncRoot = new Object();
   private Boolean _isFrozen;

   public void Freeze() { lock(_syncRoot) _isFrozen = true; }

   public void WriteValue(Object val)
   {
      lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
         if (_isFrozen)
            throw new InvalidOperationException();

      // write ...
   }
}

似乎也绝对是线程安全的,但比使用上面的 Interlocked 方法有更多的开销,所以我更倾向于尝试 3 而不是这个。

然后我至少可以想出更多(我敢肯定还有更多):

尝试 6:使用Thread.VolatileWriteand Thread.VolatileRead,但据说这些有点偏重。

尝试 7:使用Thread.MemoryBarrier,似乎有点太内部了。

尝试 8:创建不可变副本 - 不想这样做

总结:

  • 您将使用哪种尝试以及为什么(或者如果完全不同,您将如何做)?(即,发布一个值的最佳方式是什么,然后同时读取它,同时在不过度“聪明”的情况下合理高效?)
  • .NET 的内存模型“释放”写入语义是否意味着所有其他线程都看到更新(缓存一致性等)?我一般不想过多思考这个问题,但能有一个了解就好了。

编辑:

也许我的问题并不清楚,但我正在特别寻找上述尝试是好是坏的原因。请注意,我在这里谈论的是一个单一写入器的场景,该写入器写入然后在任何并发读取之前冻结。我相信尝试 1 是可以的,但我想确切地知道为什么(例如,我想知道是否可以以某种方式优化读取)。我不太关心这是否是好的设计实践,而是更关心它的实际线程方面。


非常感谢收到的问题的答复,但我自己选择将其标记为答案,因为我觉得给出的答案并不能完全回答我的问题,我不想给访问该网站的任何人留下标记的印象答案是正确的,因为由于赏金到期,它被自动标记为正确。此外,我认为票数最高的答案并没有被压倒性地投票,不足以自动将其标记为答案。

我仍然倾向于尝试 #1 是正确的,但是,我希望得到一些权威的答案。我知道 x86 有一个强大的模型,但我不想(也不应该)为特定架构编写代码,毕竟这是 .NET 的优点之一。

如果您对答案有疑问,请选择一种锁定方法,或许可以使用此处显示的优化来避免对锁定的大量争用。

4

12 回答 12

25

也许有点偏离主题,但只是出于好奇:) 为什么不使用“真正的”不变性?例如,让 Freeze() 返回一个不可变的副本(没有“写方法”或任何其他改变内部状态的可能性)并使用这个副本而不是原始对象。您甚至可以不更改状态,而是在每次写入操作时返回一个新副本(状态已更改)(afaik 字符串类可以这样做)。“真正的不变性”本质上是线程安全的。

于 2012-12-12T17:56:17.143 回答
8

我投票支持尝试 5,使用 lock(this) 实现。

这是使这项工作最可靠的方法。可以使用读/写锁,但收效甚微。只需使用普通锁即可。

_isFrozen如有必要,您可以通过首先检查然后锁定来提高“冻结”性能:

void Freeze() { lock (this) _isFrozen = true; }
object ReadValue()
{
    if (_isFrozen)
        return Read();
    else
        lock (this) return Read();
}
void WriteValue(object value)
{
    lock (this)
    {
        if (_isFrozen) throw new InvalidOperationException();
        Write(value);
    }
}
于 2012-12-03T21:50:25.333 回答
5

如果你真的在展示给其他线程之前创建、填充和冻结对象,那么你不需要任何特殊的东西来处理线程安全(.NET 的强内存模型已经是你的保证),所以解决方案 1 是有效的。

但是,如果您将未冻结的对象提供给另一个线程(或者如果您只是简单地创建类而不知道用户将如何使用它),那么使用返回一个新的完全不可变实例的解决方案可能会更好。在这种情况下,可变实例就像 StringBuilder,不可变实例就像字符串。如果您需要额外的保证,可变实例可能会检查其创建者线程,如果它被任何其他线程使用,则抛出异常(在所有方法中......以避免可能的部分读取)。

于 2013-05-06T16:23:02.573 回答
2

也许我的问题并不清楚,但我正在特别寻找上述尝试是好是坏的原因。请注意,我在这里谈论的是一个单一写入器的场景,该写入器写入然后在任何并发读取之前冻结。我相信尝试 1 是可以的,但我想确切地知道为什么(例如,我想知道是否可以以某种方式优化读取)。我不太关心这是否是好的设计实践,而是更关心它的实际线程方面。

好的,现在我更好地了解您在做什么并在寻找回复。请允许我通过首先解决您的每次尝试来详细说明我之前的回答,以促进使用锁。

尝试1:

在您的示例中,使用没有任何形式的同步原语的简单类的方法是完全可行的。由于“创作”线程是在其变异状态期间可以访问此类的唯一线程,因此这应该是安全的。如果仅当另一个线程有可能在类被“冻结”之前访问时,您需要提供同步。本质上,线程不可能缓存它从未见过的东西。

除了具有此列表内部状态的缓存副本的线程之外,您还应该关注另一个并发问题。您应该考虑通过创作线程进行写入重新排序。您的示例解决方案没有足够的代码让我解决这个问题,但是将这个“冻结”列表交给另一个线程的过程是问题的核心。您是在使用 Interlocked.Exchange 还是写入 volatile 状态?

我仍然主张这不是最好的方法,因为不能保证另一个线程在变异时没有看到该实例。

尝试2:

虽然不应使用尝试 2。如果您对成员使用原子写入,则还应该使用原子读取。我永远不会推荐一个没有另一个,因为如果没有读取和写入都是原子的,你就什么也得不到。原子读写的正确应用是您的“尝试 3”。

尝试 3:

如果线程试图改变冻结列表,这将保证抛出异常。但是,它没有断言只有在冻结的实例上才能接受读取。_isFrozen恕我直言,这与使用原子和非原子访问器访问我们的变量一样糟糕。如果您要说保护写入很重要,那么您应该始终保护读取。一个没有另一个只是“奇怪”。

忽略我自己对编写 gaurds 写但不读的代码的感觉,考虑到您的特定用途,这是一种可以接受的方法。我有一个作家,我写作,我冻结,然后我把它提供给读者。在这种情况下,您的代码可以正常工作。_isFrozen在将类交给另一个线程之前,您依赖于集合上的原子操作来提供所需的内存屏障。

简而言之,这种方法是有效的,但是如果一个线程有一个未冻结的实例,它就会中断。

尝试4:

虽然本质上这与尝试 3 几乎相同(给定一位作家),但有一个很大的不同。在这个例子中,如果你签_isFrozen入阅读器,那么每次访问都需要一个内存屏障。一旦列表被冻结,这是不必要的开销。

这仍然与尝试 3 具有相同的问题,因为_isFrozen在读取期间没有对状态进行断言,因此在您的示例用法中性能应该相同。

尝试 5:

正如我所说,这是我的偏好,因为修改为在我的其他答案中出现。

尝试 6:

与#4 基本相同。

尝试 7:

您可以使用Thread.MemoryBarrier. 基本上使用尝试 1 中的代码,您创建实例、调用Freeze()、添加您的实例Thread.MemoryBarrier,然后共享实例(或在锁中共享它)。这应该很好用,同样仅在您有限的用例下。

尝试 8:

在不了解更多信息的情况下,我无法就副本的成本提供建议。

概括

同样,我更喜欢使用具有一些线程保证或根本没有线程保证的类。在 IMO 中,创建一个仅“部分”线程安全的类是危险的。

用一位著名的绝地大师的话来说:

要么做要么不做没有尝试。

线程安全也是如此。该类应该是线程安全的或不是线程安全的。采用这种方法,您要么使用我对尝试 5 的增强,要么使用尝试 7。如果可以选择,我永远不会推荐 #7。

所以我的建议坚定地支持完全线程安全的版本。两者之间的性能成本非常小,几乎不存在。读取器线程永远不会仅仅因为您拥有一个写入器的使用场景而锁定。然而,如果他们这样做,正确的行为仍然是确定的。因此,随着您的代码随着时间的推移而发生变化,并且您的实例突然在被冻结之前被共享,您不会遇到导致程序崩溃的竞争条件。线程安全与否,不要半信半疑,否则有一天你会遇到令人讨厌的惊喜。

我的偏好是多个线程共享的所有类都是以下两种类型之一:

  1. 完全不可变。
  2. 完全线程安全。

由于冰棒列表在设计上不是一成不变的,因此它不适合 #1。因此,如果您要跨线程共享对象,它应该适合#2。

希望所有这些咆哮能进一步解释我的推理:)

_syncRoot

许多人注意到我_syncRoot在锁定实现中跳过了 a 的使用。虽然使用 _syncRoot 的理由是正确的,但它们并不总是必要的。在您有一个编写器的示例用法中,使用lock(this)应该就足够了,而无需为_syncRoot.

于 2012-12-13T20:55:18.567 回答
2

#1 - 阅读器不是线程安全的 - 我相信问题出在阅读器方面,而不是编写器(代码未显示)
#2 - 阅读器不是线程安全的 - 与 #1 相同
#3 - 有希望,可以针对大多数情况优化读取检查(当CPU 缓存同步)

尝试 3:

还可以使用 Interlocked 进行读取。

public class Foobar {

  private object _syncRoot = new object();
  private int _isFrozen = 0; // perf compiler warning, but training code, so show defaults

  // Why Exchange to 1 then throw away result.  Best to just increment.
  //public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }
  public void Freeze() { Interlocked.Increment(ref _isFrozen); }

  public void WriteValue(Object val) {
       // if this core can see _isFrozen then no special lock or sync needed
       if (_isFrozen != 0)
           throw new InvalidOperationException();

       lock(_syncRoot) {
           if (_isFrozen != 0)
               throw new InvalidOperationException(); // the 'throw' is 100x-1000x more costly than the lock, just eat it
           _val = val;
       }

  }

  public object Read() {
       // frozen is one-way, if one-way state has been published 
       // to my local CPU cache then just read _val.  
       // There are very strange corner cases when _isFrozen and _val fields are in 
       // different cache lines, but should be nearly impossible to hit unless
       // dealing with very large structs (make it more likely to cross 
       // 4k cache line).
       if (_isFrozen != 0) 
           return _val;

       // else
       lock(_syncRoot) { // _isFrozen is 0 here
           if (_isFrozen != 0) // if _isFrozen is 1 here we just collided with writer using lock on other thread, or our CPU cache was out of sync and lock() forced the dirty cache line to be read from main memory
              return _val;

           throw new InvalidOperationException(); // throw is 100x-1000x more expensive than lock, eat the cost of lock
       }          
  }

}

我认为,Joe Duffy 关于“volatile is dead”的帖子是在他的下一代 CLR/OS 架构和 ARM 上的 CLR 的背景下。我们这些做多核 x64/x86 的人我认为 volatile 很好。如果性能是主要关注点,我建议您测量上面的代码并将其与 volatile 进行比较。

与其他发布答案的人不同,如果您有很多读者(3 个或更多线程可能同时读取同一个对象),我不会直接跳到 lock()。但是在您的示例中,当发生碰撞时,您将性能敏感问题与异常混合在一起,这没有多大意义。如果您使用异常,那么您还可以使用其他更高级别的构造。

如果您想要完全安全但需要针对大量并发读者进行优化,请将 lock()/Monitor 更改为 ReaderWriterLockSlim。

.NET 具有处理发布值的新原语。看看 Rx。在某些情况下,它可以非常快速且无锁(我认为他们使用类似于上面的优化)。

如果多次写入但只保留一个值 - 在 Rx 中是“new ReplaySubject(bufferSize: 1)”。如果您尝试一下,您可能会惊讶于它的速度有多快。同时,我赞赏您尝试学习这种详细程度的尝试。

如果您想无锁,请克服对 Thread.MemoryBarrier() 的厌恶。这是极其重要的。但它具有与 Joe Duffy 所描述的 volatile 相同的陷阱——它被设计为对编译器和 CPU 的提示,以防止内存读取的重新排序(这在 CPU 方面需要很长时间,因此当没有提示存在)。当这种重新排序与函数自动内联等 CLR 结构相结合时,您会在内存和寄存器级别看到非常令人惊讶的行为。MemoryBarrier() 只是禁用 CPU 和 CLR 大部分时间使用的那些单线程内存访问假设。

于 2012-12-13T11:27:52.073 回答
2

尝试 2 在 x86 和其他具有强大内存模型的处理器上是线程安全的,但我将如何做到这一点是让线程安全成为消费者问题,因为您无法在使用的代码中有效地做到这一点。考虑:

if(!foo.frozen)
{
    foo.apropery = "avalue";
}

属性的线程安全frozen和 's setter 中的保护代码apropery并不重要,因为即使它们是完全线程安全的,您仍然有竞争条件。相反,我会这样写

lock(foo)
{
    if(!foo.frozen)
    {
        foo.apropery = "avalue";
    }
}

并且没有任何属性本质上是线程安全的。

于 2012-12-03T22:39:28.363 回答
1

事物是否被构造和写入,然后被永久冻结并多次读取?

或者你是否多次冻结和解冻并重新冻结?

如果是前者,那么“被冻结”检查可能应该在 reader 方法而不是 writer 方法中(以防止它在冻结之前读取)。

或者,如果是后者,那么您需要注意的用例是:

  • 主线程调用 writer 方法,发现没有被冻结,于是开始写
  • 在写入完成之前,有人尝试冻结对象然后从中读取,而另一个(主)线程仍在写入

在后一种情况下,谷歌显示了许多你可能会感兴趣的多读单作者的结果。

于 2012-12-03T21:49:23.377 回答
1

我不确定以下方法的成本如何,但它有点不同。只有在最初有多个线程同时尝试写入值时,它们才会遇到锁。一旦它被冻结,所有以后的调用都将直接获得异常。

尝试 9:

public class Foobar
{
    private readonly Object _syncRoot = new Object();
    private object _val;
    private Boolean _isFrozen;

    private Action<object> WriteValInternal;

    public void Freeze() { _isFrozen = true; }

    public Foobar()
    {
        WriteValInternal = BeforeFreeze;
    }

    private void BeforeFreeze(object val)
    {
        lock (_syncRoot)
        {
            if (_isFrozen == false)
            {
                //Write the values....
                _val = val;
                //...
                //...
                //...
                //and then modify the write value function
                WriteValInternal = AfterFreeze;
                Freeze();
            }
            else
            {
                throw new InvalidOperationException();
            }
        }
    }

    private void AfterFreeze(object val)
    {
        throw new InvalidOperationException();
    }

    public void WriteValue(Object val)
    {
        WriteValInternal(val);
    }

    public Object ReadSomething()
    {
        return _val;
    }
}
于 2012-12-06T23:49:20.227 回答
1

一般来说,每个可变对象都应该有一个明确定义的“所有者”;共享对象应该是不可变的。冰棒在被冻结之前不应该被多个线程访问。

就个人而言,我不喜欢暴露的“冻结”方法的冰棒免疫形式。我认为更简洁的方法是拥有AsMutableAsImmutable方法(每个方法都会在适当的时候简单地返回未修改的对象)。这种方法可以允许关于不变性的更强大的承诺。例如,如果“未共享的可变对象”在其AsImmutable成员被调用时被变异(与“未共享”的对象相反的行为),则副本中的数据状态可能是不确定的,但无论返回什么将是不可变的。相比之下,如果一个线程冻结了一个对象,然后假设它是不可变的,而另一个线程正在写入它,那么“不可变”对象可能在它被冻结并读取其值后最终发生变化。

编辑

根据进一步的描述,我建议让写入对象的代码在监视器锁中执行此操作,并让冻结例程看起来像:

public Thingie Freeze(void) // 返回有问题的对象
{
  if (isFrozen) // 私有字段
    返回这个;
  别的
    返回 DoFreeze();
}

Thingie DoFreeze(无效)
{
  if (Monitor.TryEnter(随便))
  {
    isFrozen =真;
    返回这个;
  }
  else if (isFrozen)
    返回这个;
  别的
    throw new InvalidOperationException("作者正在使用的对象");
}

Freeze方法可以被任意数量的线程调用任意次数;它应该足够短以便内联(尽管我没有对其进行分析),因此几乎不需要时间来执行。如果任何线程中对象的第一次访问是通过该Freeze方法,那应该保证在任何合理的内存模型下的适当可见性(即使线程没有看到创建并最初冻结它的线程对对象执行的更新,它将执行TryEnter,这将保证内存屏障,并且在失败之后它会注意到对象被冻结并返回它。

如果要写入对象的代码首先获得锁,则尝试写入冻结的对象可能会死锁。如果希望这样的代码抛出异常,则使用TryEnter并在无法获得锁时抛出异常。

用于锁定的对象应该是被冻结对象专有的东西。如果要冻结的对象不包含对任何东西的纯私有引用,则可以锁定this或创建一个纯粹用于锁定目的的私有对象。请注意,在不清理的情况下放弃“进入”的监视器锁是安全的;GC 将简单地忘记它们,因为如果不存在对锁的引用,那么任何人都不会关心(甚至可以询问)锁在被放弃时是否被输入。

于 2012-12-03T23:17:52.240 回答
0

你检查过懒惰吗

http://msdn.microsoft.com/en-us/library/dd642331.aspx

它使用 ThreadLocal

http://msdn.microsoft.com/en-us/library/dd642243.aspx

实际上,进一步看,有一个 Freezable 类...

http://msdn.microsoft.com/en-us/library/vstudio/ms602734(v=vs.100).aspx

于 2012-12-13T19:37:13.640 回答
0

您可以使用 POST Sharp 实现此目的

取一个接口

public interface IPseudoImmutable
{

    bool IsFrozen { get; }


    bool Freeze();
}

然后像这样从 InstanceLevelAspect 派生您的属性

 /// <summary>
/// implement by divyang
/// </summary>
[Serializable]
[IntroduceInterface(typeof(IPseudoImmutable),
    AncestorOverrideAction = InterfaceOverrideAction.Ignore, OverrideAction = InterfaceOverrideAction.Fail)]
public class PseudoImmutableAttribute : InstanceLevelAspect, IPseudoImmutable
{

    private volatile bool isFrozen;

    #region "IPseudoImmutable"


    [IntroduceMember]
    public bool IsFrozen
    {
        get
        {
            return this.isFrozen;
        }
    }


    [IntroduceMember(IsVirtual = true, OverrideAction = MemberOverrideAction.Fail)]
    public bool Freeze()
    {
        if (!this.isFrozen)
        {
            this.isFrozen = true;
        }

        return this.IsFrozen;
    }

    #endregion


    [OnLocationSetValueAdvice]
    [MulticastPointcut(Targets = MulticastTargets.Property | MulticastTargets.Field)]
    public void OnValueChange(LocationInterceptionArgs args)
    {
        if (!this.IsFrozen)
        {
            args.ProceedSetValue();
        }
    }
}


public class ImmutableException : Exception
{
    /// <summary>
    /// The location name.
    /// </summary>
    private readonly string locationName;

    /// <summary>
    /// Initializes a new instance of the <see cref="ImmutableException"/> class.
    /// </summary>
    /// <param name="message">
    /// The message.
    /// </param>
    public ImmutableException(string message)
        : base(message)
    {
    }


    public ImmutableException(string message, string locationName)
        : base(message)
    {
        this.locationName = locationName;
    }


    public string LocationName
    {
        get
        {
            return this.locationName;
        }
    }
}

然后像这样申请你的班级

    [PseudoImmutableAttribute]
public class TestClass
{



    public string MyString { get; set; }




    public int MyInitval { get; set; }
}

然后在多线程中运行它

 /// <summary>
/// The program.
/// </summary>
public class Program
{
    /// <summary>
    /// The main.
    /// </summary>
    /// <param name="args">
    /// The args.
    /// </param>
    public static void Main(string[] args)
    {
        Console.Title = "Divyang Demo ";
        var w = new Worker();
        w.Run();
        Console.ReadLine();
    }
}


internal class Worker
{

    private object SyncObject = new object();


    public Worker()
    {
        var r = new Random();
        this.ObjectOfMyTestClass = new MyTestClass { MyInitval = r.Next(500) };
    }


    public MyTestClass ObjectOfMyTestClass { get; set; }


    public void Run()
    {

        Task readWork;

        readWork = Task.Factory.StartNew(

            action: () =>
                {
                    for (;;)
                    {
                        Task.Delay(1000);
                        try
                        {
                            this.DoReadWork();
                        }
                        catch (Exception exception)
                        {
                            // Console.SetCursorPosition(80,80);
                            // Console.SetBufferSize(100,100);
                            Console.WriteLine("Read Exception : {0}", exception.Message);
                        }
                    }
                    // ReSharper disable FunctionNeverReturns
                });


        Task writeWork;


        writeWork = Task.Factory.StartNew(

            action: () =>
                {
                    for (int i = 0; i < int.MaxValue; i++)
                    {
                        Task.Delay(1000);
                        try
                        {
                            this.DoWriteWork();
                        }
                        catch (Exception exception)
                        {
                            Console.SetCursorPosition(80, 80);
                            Console.SetBufferSize(100, 100);
                            Console.WriteLine("write Exception : {0}", exception.Message);
                        }

                        if (i == 5000)
                        {

                            ((IPseudoImmutable)this.ObjectOfMyTestClass).Freeze();

                        }
                    }
                });

        Task.WaitAll();
    }

    /// <summary>
    /// The do read work.
    /// </summary>
    public void DoReadWork()
    {
        // ThreadId  where reading is done
        var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

        // printing on screen
        lock (this.SyncObject)
        {
            Console.SetCursorPosition(0, 0);
            Console.SetBufferSize(290, 290);
            Console.WriteLine("\n");
            Console.WriteLine("Read Start");
            Console.WriteLine("Read => Thread Id: {0} ", threadId);
            Console.WriteLine("Read => this.objectOfMyTestClass.MyInitval: {0} ", this.ObjectOfMyTestClass.MyInitval);
            Console.WriteLine("Read => this.objectOfMyTestClass.MyString: {0} ", this.ObjectOfMyTestClass.MyString);


            Console.WriteLine("Read End");
            Console.WriteLine("\n");
        }
    }

    /// <summary>
    /// The do write work.
    /// </summary>
    public void DoWriteWork()
    {
        // ThreadId  where reading is done
        var threadId = System.Threading.Thread.CurrentThread.ManagedThreadId;

        // random number generator
        var r = new Random();
        var count = r.Next(15);

        // new value for Int property
        var tempInt = r.Next(5000);
        this.ObjectOfMyTestClass.MyInitval = tempInt;

        // new value for string Property
        var tempString = "Randome" + r.Next(500).ToString(CultureInfo.InvariantCulture);

        this.ObjectOfMyTestClass.MyString = tempString;


        // printing on screen
        lock (this.SyncObject)
        {
            Console.SetBufferSize(290, 290);
            Console.SetCursorPosition(125, 25);

            Console.WriteLine("\n");
            Console.WriteLine("Write Start");
            Console.WriteLine("Write => Thread Id: {0} ", threadId);
            Console.WriteLine("Write => this.objectOfMyTestClass.MyInitval: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyInitval, tempInt);
            Console.WriteLine("Write => this.objectOfMyTestClass.MyString: {0} and New Value :{1} ", this.ObjectOfMyTestClass.MyString, tempString);
                          Console.WriteLine("Write End");
            Console.WriteLine("\n");
        }
    }
}





but still it will allow you to change property like array ,list . but if you apply more login in that then it may work for all type of property and field
于 2013-12-20T16:11:35.537 回答
0

受 C++ 可移动类型的启发,我会做这样的事情。请记住不要在冻结/解冻后访问该对象。

当然,_data != null如果您想清楚用户在解冻/冻结后访问时为什么会获得 NRE,您可以添加检查/抛出。

public class Data 
{
   public string _foo;
   public int _bar;
}

public class Mutable 
{
   private Data _data = new Data();

   public Mutable() {}

   public string Foo { get => _data._foo; set => _data._foo = value; }
   public int Bar { get => _data._bar; set => _data._bar = value; }

   public Frozen Freeze() 
   { 
      var f = new Frozen(_data);
      _data = null;
      return f;
   }
}

public class Frozen 
{
   private Data _data;

   public Frozen(Data data) => _data = data;              

   public string Foo => _data._foo;  
   public int Bar => _data._bar;

   public Mutable Thaw() 
   { 
      var m = new Mutable(_data);
      _data = null;
      return m;
   }
}
于 2018-02-19T20:04:01.547 回答