代码之家  ›  专栏  ›  技术社区  ›  AidanO

我想知道有没有更好的方法来实现这个“简单锁”

  •  2
  • AidanO  · 技术社区  · 15 年前

    有没有更好的方法来实现下面这样的简单锁?

    我只想“剂量测定”,如果它还没有运行。我应该在这里使用真锁吗?如果我使用锁,会导致所有的事情排队等待锁释放吗?(这不是我想要的!)

    谢谢

      bool running = false;
    
      void DataDisplayView_Paint(object sender, PaintEventArgs e)
      {
        // if (!this.initialSetDone)  
         if (!running)
         {
            this.running = true;
    
            //DOSOMETHING
    
            this.running = false;
         }
     }
    
    8 回复  |  直到 15 年前
        1
  •  5
  •   Brian Gideon    15 年前

    不,您不想在这里使用锁。这不是线程同步问题。这是一个方法重入问题。

    你可以试试这个。

    bool running = false; 
    
    void DataDisplayView_Paint(object sender, PaintEventArgs e) 
    { 
      if (!this.running)
      {
        this.running = true; 
        try
        {
          //DOSOMETHING 
        }
        finally
        {
          this.running = false; 
        }
      }
    }
    
        2
  •  2
  •   Grzenio    15 年前

    您只需要同步(锁定是最简单的方式)代码位:

    bool running = false;
    readonly object padlock = new object();
    
      void DataDisplayView_Paint(object sender, PaintEventArgs e)
      {
    
         if (!this.initialSetDone)
         {
            lock(padlock)
            {
              if(running) return;
              running = true;
            }
            try {
    
              //DOSOMETHING
            }
            finally
            {
              lock(padlock)
              {
                this.running = false;
              }
            }
         }
     }
    
        3
  •  1
  •   JaredPar    15 年前

    最好的方法是使用Try/Finally块

    try { 
      this.running = true;
      ...
    } finally {
      this.running = false;
    }
    

    只有从多个线程调用此方法时才需要真正的线程锁。考虑到它似乎是一个画图事件处理程序,这是不太可能的,因为控件被附加到一个线程上。

        4
  •  1
  •   Dan Tao    15 年前

    我错过什么了吗?您发布的代码似乎没有任何作用。也就是说,无论代码是否运行 running 是真的。

    通常,任何试图像这样“锁定”自身的代码…

    if (!running)
    {
        running = true;
    
        try
        {
            // This code should not call itself recursively.
            // However, it may execute simultaneously on more than one thread
            // in very rare cases.
        }
        finally
        {
            running = false;
        }
    }
    

    …非常好,只要您处于单线程场景中。如果您运行的是多线程代码,那么可能会出现问题,因为您假设没有两个线程能够到达 if (!running) 同时排成一行。

    多线程代码中的解决方案是使用某种形式的原子开关。我已经用过了 AutoResetEvent 为此目的:

    var ready = new AutoResetEvent(true);
    
    if (ready.WaitOne(0))
    {
        try
        {
            // This code will never be running on more than one thread
            // at a time.
        }
        finally
        {
            ready.Set();
        }
    }
    
        5
  •  1
  •   Dan Bryant    15 年前

    请注意,如果您的paint回调具有可重入性,则会遇到更严重的问题。油漆处理程序应该阻塞您的消息泵(并且应该相对快速地完成),所以您不应该看到这种情况。唯一的例外是,如果您从画图处理程序的某个地方调用application.doEvents(),那么您实际上不应该这样做。

        6
  •  0
  •   James Curran    15 年前

    你把不同的名字放在中间,所以我假设你想要:

      bool running = false; 
    
      void DataDisplayView_Paint(object sender, PaintEventArgs e) 
      { 
         if (!this.running) 
         { 
            this.running = true; 
    
            //DOSOMETHING 
    
            this.running = false; 
         } 
     }
    

    这里的问题是,如果可以从多个线程调用dataDisplayView_paint,那么 if (!this.running) 以及 this.running = true; 另一个线程可以跳入并开始剂量测量(因为运行仍然是错误的)。然后第一个线程将恢复,并再次开始剂量测量。如果这是可能的,那么您需要使用真正的锁。

        7
  •  0
  •   theburningmonk    15 年前

    如果使用monitor.tryenter,则可以指定超时,在这种情况下,您得到的结果如下:

    • 一次只能有一个线程运行剂量测量
    • 随后的调用将尝试获取锁,并在timeout子句之后放弃。

    如果您不提供超时,或将超时设置为0,则此调用不会阻塞,并将立即返回(可能更适合您的要求?):

    if (!this.initialSetDone && Monitor.TryEnter(_lock))
    {
       // DOSOMETHING
    }
    

    或者,您可以 running 变量volatile,以便始终获取存储在变量中的最新值:

    private volatile bool running;
    
    if (!this.initialSetDone && !this.running)  // #1
    {
       this.running = true;
       try
       {
         // DOSOMETHING
       }
       finally
       {
         this.running = false;
       }
    }
    

    第二种方法不会对后续调用进行排队,但有可能两个线程都会命中1并评估继续进行是安全的,然后都会运行dosometing,尽管这是非常不可能的。

        8
  •  0
  •   Juliet    15 年前

    我只想“剂量测定”如果 还没跑呢

    您的问题没有足够的信息,因此我不得不对您的代码进行假设。

    • 我的第一个假设是,根据签名 DataDisplayView_Paint(object s, PaintEventArgs e) ,您的代码在GUI线程上运行。

    • 我的第二个假设是你的代码 DOSOMETHING 是同步的。

    考虑到这一点,这里是您的代码版本,它保证我们只运行 剂量测定法 如果尚未运行:

    void DataDisplayView_Paint(object s, PaintEventArgs e)
    {
        //DOSOMETHING
    }
    

    GUI线程一次只处理一条消息,而 DataDisplayView_Paint 方法不退出,直到 剂量测定法 完成。如果您使用图形用户界面(GUI)绘制图形对象或更改标签,则不会从多个线程调用此代码——如果这样做,.NET将抛出异常。换句话说,你 不要 需要任何同步。


    让我们假设 剂量测定法 异步运行——现在我们有了一个有趣的问题,但很容易解决,而且您不需要任何bools。

    本质上,您所要做的就是在dosomething运行时禁用事件处理程序,然后重新启用它。不使用bool,而是根据需要取消挂起并重新挂起事件处理程序:

    void DataDisplayView_Paint(object s, PaintEventArgs e)
    {
        DataDisplayView.Paint -= DataDisplayView_Paint;
        DoSomethingAsynchronously(); // re-hooks event handler when completed
    }
    
    void DoSomethingAsychronously()
    {
        ThreadPool.QueueUserWorkItem(() =>
        {
            try
            {
                // DOSOMETHING
            }
            finally
            {
                // may need a lock around this statement
                DataDisplayView.Paint += DataDisplayView_Paint;
            }
        });
    }