10

我正在设计一个基类,当它被继承时,它将在多线程环境中针对上下文提供业务功能。每个实例都可能有长时间运行的初始化操作,所以我想让对象可重用。为此,我需要能够:

  1. 将上下文分配给这些对象之一以允许其完成工作
  2. 防止对象在已有上下文时被分配新上下文
  3. 防止在对象没有上下文时访问某些成员

此外,每个上下文对象可以由许多工作对象共享。

是否有适合我想要做的正确同步原语?这是我想出的最适合我需要的模式:

private Context currentContext;

internal void BeginProcess(Context currentContext)
{
    // attempt to acquire a lock; throw if the lock is already acquired,
    // otherwise store the current context in the instance field
}

internal void EndProcess()
{
    // release the lock and set the instance field to null
}

private void ThrowIfNotProcessing()
{
    // throw if this method is called while there is no lock acquired
}

使用上述内容,我可以保护不应访问的基类属性和方法,除非对象当前处于处理状态。

protected Context CurrentContext
{
    get
    {
        this.ThrowIfNotProcessing();
        return this.context;
    }
}

protected void SomeAction()
{
    this.ThrowIfNotProcessing();

    // do something important
}

我最初的目的是使用Monitor.Enter和相关的功能,但这并不能阻止同线程重入(BeginProcess对原始线程的多次调用)。

4

4 回答 4

13

.NET 中有一个不可重入的同步对象,您正在寻找一个信号量。

在你承诺之前,一定要把你的鸭子排成一排,问问自己,BeginProcess() 怎么可能在同一个线程上再次被调用。这是非常非常不寻常的,您的代码必须可重入才能发生。这通常只发生在具有调度程序循环的线程上,GUI 应用程序的 UI 线程就是一个常见的例子。如果这确实可行并且您实际上使用了信号量,那么您也将处理后果,您的代码将死锁。因为它递归到 BeginProcess 并在信号量上停止。因此永远不会完成,也永远无法调用 EndProcess()。Monitor 和 Mutex 可重入是有充分理由的 :)

于 2013-09-26T01:39:03.093 回答
10

您可以使用Semaphore.NET Framework 2.0 附带的类。

信号量的一个很好的用途是同步有限数量的资源。在您的情况下,您似乎拥有Context想要在消费者之间共享的资源。

您可以创建一个信号量来管理资源,例如:

var resourceManager = new Semaphore(0, 10);

然后使用以下方法等待资源可用BeginProcess

resourceManager.WaitOne();

最后EndProcess使用以下方法释放资源:

resourceManager.Release();

这是一个关于在像您这样的情况下使用信号量的好博客

https://web.archive.org/web/20121207180440/http://www.dijksterhuis.org/using-semaphores-in-c/

于 2013-09-26T01:45:21.737 回答
-1

有一种非常简单的方法可以防止重新进入(在一个线程上):

private bool bRefresh = false;
private void Refresh()
{
  if (bRefresh) return;
  bRefresh = true;
  try
  {
    // do something here
  }
  finally
  {
    bRefresh = false;
  }
}
于 2014-09-28T11:51:37.653 回答
-1

该类Interlocked可用于退出方法的线程安全解决方案,而不是在进行重入调用时阻塞。像 Vlad Gonchar 解决方案,但线程安全。


    private int refreshCount = 0;
    private void Refresh()
    {
      if (Interlocked.Increment(ref refreshCount) != 1) return;
        
      try
      {
        // do something here
      }
      finally
      {
        Interlocked.Decrement(ref refreshCount);
      }
    }

于 2021-12-09T14:27:18.103 回答