【问题标题】:How to make public properties thread-safe?如何使公共属性线程安全?
【发布时间】:2012-09-11 15:36:15
【问题描述】:

由于某种原因,我正在实现 BackgroundWorker 替换,并且我必须实现以下公共属性:

public bool CancellationPending { get; private set; }
public bool IsBusy { get; private set; }
public bool WorkerReportsProgress { get; set; }
public bool WorkerSupportsCancellation { get; set; }

我确定您知道它们在 BackgroundWorker 中的用途。因此它们可能会被不同的线程访问/修改。我担心如何“保护”它们以进行多线程处理。我认为将它们声明为 volatile 就足够了,但 volatile 不能应用于自动属性。

我该怎么办?我应该为这些属性创建私有字段,并声明它们volatile?还是应该在每个 getset 块中使用 locking?

我认为这应该是非常常见的场景 - 使属性(最好是自动属性)线程安全。请注意,此示例中的所有属性都是原子类型。

编辑:

澄清我需要什么:我需要确保所有线程始终读取属性的最新值。看到这个:https://stackoverflow.com/a/10797326/1081467

同样,你建议使用volatile,或locking,或其他什么?.. 使用bool 属性时,原子性得到保证,所以只剩下第二个问题(阅读最新的-date 值),那么你如何正确解决这个问题?当你有非原始类型的属性时怎么办?您是否将locks 放在每个getset 块中?

【问题讨论】:

  • 你不能使用BackgroundWorker类?
  • 在每个 getset 块内使用 locking。或者,使您的类型不可变。例如,CancellationPending 不太可能有问题。
  • 在这种情况下,thread safe 到底是什么意思?
  • 看来至少IsBusy 应该是一个事件,以便您可以等待它。也许其余的可以使用Interlocked.CompareExchange
  • @TX_ 你为什么不用Tasks 和STA scheduler 一起使用?

标签: c# .net multithreading thread-safety


【解决方案1】:

我想出了以下实现。请评论您是否认为这是最佳解决方案:

//========== Public properties ==================================================//

public bool CancellationPending { get { return _cancellationPending; } private set { _cancellationPending = value; } }

public bool IsBusy { get { return _isBusy; } private set { _isBusy = value; } }

public bool WorkerReportsProgress { get { return _workerReportsProgress; } set { _workerReportsProgress = value; } }

public bool WorkerSupportsCancellation { get { return _workerSupportsCancellation; } set { _workerSupportsCancellation = value; } }

//========== Private fields ==================================================//

private volatile bool _cancellationPending;
private volatile bool _isBusy;
private volatile bool _workerReportsProgress;
private volatile bool _workerSupportsCancellation;

推理:原子性是由字段类型为bool这一事实确保的,因此不需要locking。将它们设为volatile 将确保任何线程都将读取当前值——不缓存——以防另一个线程对其进行了修改。我认为这是 volatile 关键字的确切用途(也是唯一有效用途),对吧?

【讨论】:

  • 无法为属性定义波动率。基础字段才是最重要的。
  • class Program { static bool _stop = false; static bool stop { get { return _stop; } set { _stop = value; } } public static void Main(string[] args) { new Thread(() => { Console.WriteLine("START"); bool dummy = false; while (!stop) dummy = !dummy; Console.WriteLine("END"); }).Start(); Thread.Sleep(1000); stop = true; Console.WriteLine("stop = true"); Console.WriteLine("WAITING..."); } }
  • 复制并粘贴它,然后构建release 构建,并在没有调试器的情况下运行。它会挂起。然后将static bool _stop = false; 更改为static volatile bool _stop = false;,做同样的事情,你会看到 - 它不再挂起(如所希望的那样)。现在请撤消您的投票。
  • 这很有趣,因为是你在浪费我的时间,兄弟。尽管我 100% 确定,但你的话让我怀疑,我决定再次测试。我写了那个清楚地说明问题的小代码。我正在使用 VS2012,尽管我已经使用 VS2010 进行了测试,并且行为完全相同。在 Visual C# 中创建新的空项目。复制/粘贴代码。构建release 构建,这很重要!然后在调试器之外运行它,这也很重要!你会看到在写“WAITING...”后原始代码会挂起,如果你让_stop字段变为volatile,它会起作用。
  • TX 是对的,代码挂了。我在生产中遇到了同样的问题,我依靠 bool 属性来停止处理循环。现在我完全切换到了 CancellationTokenSource,从实现可靠取消中提取了很多实现细节。
【解决方案2】:
public bool CancellationPending { get; private set; }
public bool IsBusy { get; private set; }
public bool WorkerReportsProgress { get; set; }
public bool WorkerSupportsCancellation { get; set; }

因此它们可能被不同的线程访问/修改

不,这仅适用于 CancellationPendingIsBusy,不适用于其他人。
它们都是布尔值,保证是原子的。原子性在这里就足够了。

原始 Backgroundworker 的所有属性都记录为线程安全。
this page底部附近。

【讨论】:

  • 做到了。同样,您可能不应该做任何事情。见上面的评论。
  • 即使它们是原子的,也有必要将支持字段标记为 volatile 以防止编译器优化假定从单个线程进行访问。
  • @MateenUlhaq - 你不能让属性变得易变,你也不必这样做。 Bgw 提供了足够的 fences 来使其可靠。例如,请参阅this
【解决方案3】:

虽然我认为有更好的选择,例如使用 Tasks 和 svick 提到的另一个调度程序。

如果您想继续走这条路,您绝对应该使用锁定字段而不是易失字段,如 volatile does not do what you thinkOh and this guy said something about never making a volatile field...

您可以根据访问特性使用您最喜欢的同步原语(lock、Mutex、Interlocked、ReaderWriterLockSlim 等)代替 volatile。

【讨论】:

    【解决方案4】:

    一种简单的方法是为您希望线程安全的每个属性设置互斥对象。在获取和设置属性中,使用 Monitor.Enter(declaredObjectMutext) 和 Monitor.Exit(declaredObjectMutex)。完成后,这将使您的属性成为线程安全的(所有对 get 和 set 的调用都将阻塞调用,直到任何其他线程完成为止)。

    另一个选择是使用互锁类,它允许对整数和布尔值进行线程安全修改。如果这就是您对属性使用的全部内容,那么它是一个简单的解决方案。

    【讨论】:

      猜你喜欢
      • 2011-06-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多