代码之家  ›  专栏  ›  技术社区  ›  Romain Verdier

如何在多线程上下文中使方法独占?

  •  3
  • Romain Verdier  · 技术社区  · 16 年前

    我有一个方法应该以独占的方式执行。基本上,它是一个多线程应用程序,方法由计时器定期调用,但也可以由用户操作手动触发。

    让我们举个例子:

    1. 计时器超时,因此方法是 打电话。这项任务可能需要一些时间 秒。

    2. 之后,用户单击 按钮,应触发 同一任务:BAM。它什么也不做 因为方法已经在运行。

    我使用了以下解决方案:

    public void DoRecurentJob()
    {
        if(!Monitor.TryEnter(this.lockObject))
        {
            return;
        }
    
        try
        {
            // Do work
        }
        finally 
        {
            Monitor.Exit(this.lockObject);
        }
    }
    

    在哪里? lockObject 声明如下:

    private readonly object lockObject = new object();
    

    编辑 :只有一个包含此方法的对象实例,因此我将锁定对象更新为非静态。

    有更好的方法吗?或者这个可能是错误的,因为什么原因?

    8 回复  |  直到 7 年前
        1
  •  4
  •   Mats Fredriksson    16 年前

    如果您只是对不让方法并行运行感兴趣,那么这看起来是合理的。没有什么可以阻止它在彼此之后立即运行,比如说在计时器执行monitor.exit()半微秒后按下按钮。

    将锁对象设置为只读静态也是有意义的。

        2
  •  2
  •   Christoffer Lette    16 年前

    你也可以用 Mutex Semaphore 如果您希望它跨进程工作(有轻微的性能损失),或者如果您需要设置除一个允许同时运行代码段的线程以外的任何其他数字。

    还有其他的信令结构可以工作,但是您的示例看起来像它可以做到这一点,并且以一种简单而直接的方式。

        3
  •  2
  •   Jon Skeet    16 年前

    小问题:如果lockobject变量是静态的,那么“this.lockobject”不应该编译。尽管这是一个实例方法,但它也有明显的类型范围行为,这也让人觉得有点奇怪(至少应该有大量文档记录)。可能使它成为一个以实例为参数的静态方法?

    它是否实际使用实例数据?如果没有,则使其静止。如果是这样,您至少应该返回一个布尔值来说明您是否使用实例进行了工作-我发现很难想象这样一种情况,即我希望使用特定的数据块完成某些工作,但我不在乎该工作是否没有执行,因为某些类似的工作是使用不同的数据块执行的。

    我觉得它应该管用,但确实有点奇怪。我不太喜欢使用手动锁,只是因为它很容易出错——但这看起来不错。(您需要考虑“if”和“try”之间的异步异常,但我怀疑它们不会是问题-我不记得clr做出的确切保证。)

        4
  •  2
  •   Kimoz    16 年前

    我认为微软 recommends 使用 lock 语句,而不是直接使用monitor类。它提供了一个更干净的布局,并确保锁在所有情况下都被释放。

    public class MyClass
    {
    
      // Used as a lock context
      private readonly object myLock = new object();
    
      public void DoSomeWork()
      {
        lock (myLock)
        {
          // Critical code section
        }
      }
    }
    

    如果应用程序需要该锁跨越MyClass的所有实例,则可以将锁上下文定义为静态字段:

    private static readonly object myLock = new object();
    
        5
  •  1
  •   Robert Paulson    16 年前

    代码是可以的,但会同意将方法改为静态的,因为它更好地传达了意图。奇怪的是,类的所有实例之间都有一个同步运行的方法,但该方法不是静态的。

    请记住,静态同步方法始终可以是受保护的或私有的,只对类的实例可见。

    public class MyClass
    { 
        public void AccessResource()
        {
            OneAtATime(this);
        }
    
        private static void OneAtATime(MyClass instance) 
        { 
           if( !Monitor.TryEnter(lockObject) )
           // ...
    
        6
  •  0
  •   Mendelt    16 年前

    这是一个很好的解决方案,尽管我对静态锁不太满意。现在你不用等锁了,这样你就不会遇到死锁的麻烦了。但是,如果将锁设置得太明显,则在下次编辑此代码时很容易陷入麻烦。而且,这不是一个非常可扩展的解决方案。

    我通常尝试将我试图保护的所有资源都设置为不被类的多线程私有实例变量访问,然后将锁也设置为私有实例变量。这样,如果需要缩放,可以实例化多个对象。

        7
  •  0
  •   community wiki bzlm    16 年前

    一种更具声明性的方法是使用 MethodImplOptions.Synchronized 要同步访问的方法的说明符:

    [MethodImpl(MethodImplOptions.Synchronized)] 
    public void OneAtATime() { }
    

    但是,不鼓励使用这种方法。 有几个原因,其中大部分可以找到 here here . 我把这个贴出来,这样你就不会想用它了。在Java中, synchronized 是关键字,因此在查看线程模式时可能会出现。

        8
  •  0
  •   shannon    7 年前

    我们有一个类似的要求,附加的要求是,如果再次请求长时间运行的进程,那么它应该在当前循环完成后排队执行另一个循环。与此类似:

    https://codereview.stackexchange.com/questions/16150/singleton-task-running-using-tasks-await-peer-review-challenge

    private queued = false;
    private running = false;
    private object thislock = new object();
    
    void Enqueue() {
        queued = true;
        while (Dequeue()) {
            try {
                // do work
            } finally {
                running = false;
            }
        }
    }
    
    bool Dequeue() {
        lock (thislock) {
            if (running || !queued) {
                return false;
            }
            else
            {
                queued = false;
                running = true;
                return true;
            }
        }
    }