【问题标题】:Can I call Monitor.Pulse from a different class in C#我可以从 C# 中的不同类调用 Monitor.Pulse
【发布时间】:2026-01-31 00:45:01
【问题描述】:

在我正在开发的应用程序中,我将使用 2 个线程来执行各种操作。 (这里我就不细说了。)这些线程循环工作,检查是否有工作要做,做工作,计算需要等待和等待的时间。 (见下文)

public Global : System.Web.HttpApplication
{
  private static Thread StartingDateThread;
  private static Thread DeadlineDateThread;
  private static object o1;
  private static object o2;

  public static Thread GetStartingDateThreadInstance
  {
     get
       {
         if(StartingDateThread==null)
          {
             StartingDateThread=new Thread(new ThreadStart(MonitorStartingDates));
          }
          return StartingDateThread;
       }
  }

public static Thread GetDeadlineThreadInstance
  {
     get
       {
         if(DeadlineDateThread==null)
          {
             DeadlineDateThread=new Thread(new ThreadStart(MonitorDeadlines));
          }
          return DeadlineDateThread;
       }
  }

 public static object GetFirstObjectInstance
  {
     get
       {
         if(o1==null)
          {
             o1=new object();
          }
          return o1;
       }
  }

 public static object GetSecondObjectInstance
   {
     get
      {
        if(o2==null)
         {
          o2=new object();
         }
         return o2;
      }
  }

  protected void Application_Start(object sender, EventArgs e)
  {
      GetStartingDateThreadInstance.Start();
      GetDeadlineThreadInstance.Start();
      //////////////////////
      ////Do other stuff.
  }


   public void MonitorStartingDates()
   {
      while(true)
      {
           //Check if there is stuff to do.
           //Do stuff if available.
           //Check if there will be stuff to do in the future and if there is, check
           //the time to wake up.
           //If there is nothing to do, sleep for a pre-determined 12 hours.

         if(StuffToDoInFuture)
         {
          Monitor.Enter(GetFirstObjectInstance);
          Monitor.Wait(WaitingTime);
          Monitor.Exit(GetFirstObjectInstance);
         }
         else
         {
          Monitor.Enter(GetFirstObjectInstance);
          Monitor.Wait(new TimeSpan(12, 0, 0));
           Monitor.Exit(GetFirstObjectInstance);
         }
      }
   }

  public void MonitorDeadlines()
  {
      while(true)
      {
           //Check if there is stuff to do.
           //Do stuff if available.
           //Check if there will be stuff to do in the future and if there is, check
           //the time to wake up.
           //If there is nothing to do, sleep for a pre-determined 3 days and 12 hours.

         if(StuffToDoInFuture)
         {
          Monitor.Enter(GetSecondObjectInstance);
          Monitor.Wait(WaitingTime);
          Monitor.Exit(GetSecondObjectInstance);
         }
         else
         {

             Monitor.Enter(GetSecondObjectInstance);
             Monitor.Wait(new TimeSpan(3, 12, 0, 0));
             Monitor.Exit(GetSecondObjectInstance);
         }

      }
  }

如您所见,这两个线程是在 asax 文件的 Application_Start 方法中启动的。如果有可做的事情,它们就会运行,然后计算需要等待的时间段,然后等待。但是,当 Web 应用程序的用户执行操作时,新记录将被插入到数据库中,并且在某些情况下,两个线程中的任何一个都必须比计划更早地恢复操作。因此,假设我的 DataAccess 类中有一个方法可以将新数据插入数据库。 (见下文)

  public class DataAccess
  {
      ///////////////
      //
      public void InsertNewAuction()
      {
        ///Insert new row calculate the time
          Monitor.Pulse(Global.GetFirstObjectInstance);
          Monitor.Pulse(Global.GetSecondObjectInstance);
       ///
      }
   }

这似乎是一个无效的操作,因为在从 InsertNewAuction 方法调用 Monitor.Pulse 的阶段我得到了一个异常。诸如“从未同步的代码块调用对象同步方法”之类的东西。有没有办法做到这一点?感谢您的帮助

【问题讨论】:

  • 为什么不使用发布/订阅模型? ala ServiceBus

标签: c# multithreading thread-synchronization


【解决方案1】:

至于您看到的具体错误,这是因为必须在监视器锁内调用 Monitor.Pulse,就像这样(我使用了锁而不是 Enter/Exit,因为确保锁是更安全的总是释放,因为它使用了适当的 try/finally 块):

lock (Global.GetFirstObjectInstance)
{
   Monitor.Pulse(Global.GetFirstObjectInstance);
}

关于这里更一般的设计问题,将锁对象公开为公共(或更糟糕的是,全局)字段通常很危险。特别是,当多个全局锁以不同的顺序暴露和获取时,或者当您在持有锁的同时阻塞对 UI 线程的调度等情况时,它可能会导致死锁。考虑寻找替代方法来完成您所追求的目标。

【讨论】:

    【解决方案2】:

    如另一个答案中所述,您必须先获取锁,然后才能在监视器对象上调用 Monitor.Pulse()

    也就是说,您的代码至少还有一个严重的错误:您没有以线程安全的方式初始化同步对象,这很容易导致两个不同的线程使用两个不同的对象实例,从而导致它们之间没有同步线程:

    public static object GetFirstObjectInstance
    {
        get
        {
            if(o1==null)
            {
                o1=new object();
            }
            return o1;
        }
    }
    

    如果两个线程同时调用这个getter,它们每个都可能将o1 视为null 并尝试初始化它。然后每个对象实例都可能返回不同的值。

    您应该在初始化程序中简单地初始化对象:

    private static readonly object o1 = new object();
    

    然后从 getter 中返回:

    public static object GetFirstObjectInstance { get { return o1; } }
    

    这解决了线程安全问题。但是您仍然对代码有其他问题。首先,您应该将同步封装在一个对象中,而不是暴露实际的同步object 实例。其次,假设您要公开同步对象,我不明白您为什么要打扰该属性,因为您将该字段公开。如果您还想使用属性,该字段应该是私有的。

    如果属性遵循正常的 .NET 命名约定也会更好。返回对象的方法的名称中会包含“Get”,但属性中不会。只需将其命名为“FirstObjectInstance”即可。

    也如 Dan 所述,在您想要获取锁的任何地方使用 lock

    代码中可能还有其他问题...我没有进行彻底的审查。但上述情况你需要确定。

    【讨论】:

    • 感谢您的回答。顺便说一句,我并不是要公开这些字段。只是将它们固定为私有。
    最近更新 更多