3

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

让我们举个例子:

  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();

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

有更好的方法吗?或者也许这个只是出于某种原因是错误的?

4

8 回答 8

4

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

并且将锁定对象设置为只读静态也很有意义。

于 2008-09-29T09:36:39.383 回答
2

您也可以使用Mutex,或者Semaphore如果您希望它跨进程工作(性能略有下降),或者如果您需要设置任何其他数量,而不是允许运行您的代码的同时线程之一。

还有其他可以工作的信号结构,但是您的示例看起来可以解决问题,并且以简单直接的方式。

于 2008-09-29T09:36:53.297 回答
2

次要问题:如果 lockObject 变量是静态的,则“this.lockObject”不应编译。虽然这是一个实例方法,但它也具有明显的类型范围行为,这也让人感觉有点奇怪(并且至少应该有大量文档)。可能使它成为一个以实例为参数的静态方法?

它实际上是否使用实例数据?如果没有,请将其设为静态。如果是这样,您至少应该返回一个布尔值来说明您是否对实例进行了工作 - 我发现很难想象我想要对特定数据完成一些工作的情况,但我不关心是否因为使用不同的数据执行了一些类似的工作而没有执行该工作。

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

于 2008-09-29T09:39:13.777 回答
2

我认为微软建议使用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();
于 2008-09-29T10:34:35.873 回答
1

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

请记住,您始终可以让静态同步方法受到保护或私有,使其仅对类的实例可见。

public class MyClass
{ 
    public void AccessResource()
    {
        OneAtATime(this);
    }

    private static void OneAtATime(MyClass instance) 
    { 
       if( !Monitor.TryEnter(lockObject) )
       // ...
于 2008-09-29T10:02:10.787 回答
0

这是一个很好的解决方案,尽管我对静态锁并不满意。现在你不需要等待锁,所以你不会遇到死锁的麻烦。但是,让锁太显眼很容易让您在下次必须编辑此代码时遇到麻烦。这也不是一个非常可扩展的解决方案。

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

于 2008-09-29T10:08:44.267 回答
0

一种更具声明性的方法是在您希望同步访问的方法上使用MethodImplOptions.Synchronized说明符:

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

但是,不鼓励使用此方法有几个原因,其中大部分可以在此处此处找到。我发布了这个,所以你不会想使用它。在 Java 中,synchronized是一个关键字,因此在查看线程模式时可能会出现。

于 2008-10-01T11:55:10.953 回答
0

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

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;
        }
    }
}
于 2017-05-31T20:23:07.900 回答