【问题标题】:Single instance .NET Core App (or making crontab run only 1 instance of my app)单实例 .NET Core 应用程序(或使 crontab 仅运行我的应用程序的 1 个实例)
【发布时间】:2018-04-10 20:37:52
【问题描述】:

我想在 Linux 中使用 crontabschedule 上执行 .NET Core 应用程序。这是一个长时间运行的操作,如果之前的执行尚未完成,我不希望运行另一个实例。换句话说,我不希望 crontab 在给定时间执行我的 .NET Core 应用程序的多个实例。

有什么办法可以避免吗?我不想修改我的应用程序的代码。也许 crontab 有一个选项可以避免并发。我还不是 Linux 专家(还):)

【问题讨论】:

    标签: c# .net linux cron .net-core


    【解决方案1】:

    对于那些想从代码中检查实例的人,您可以使用这样的命名互斥锁

    const string mutexName = @"Global\appName";
    
    var mutex = new Mutex(true, mutexName, out var createdNew);
    
    if (!createdNew)
    {
        Console.WriteLine(mutexName + " is already running! Exiting the application.");
        return;
    }
    

    确保您的互斥锁名称以 "Global\" 开头。

    【讨论】:

    • Mutex 具有线程亲和性,这意味着您不能将它与async 代码一起使用,因此如果可以使用它,Semaphore 是更好的选择(不支持命名为Semaphores例如,目前在 Linux 下)。
    • @13xforever 在程序开始执行检查时你真的需要异步代码吗?
    • @xneg 在您使用新的async Task Main() {} 签名时很有帮助
    【解决方案2】:

    我终于使用了一个可用于 Raspbian 的小工具:flock

    在我的 crontab 配置文件中,我放了这个:

    flock -n /tmp/importer.lock dotnet ~/Desktop/Importer/Plugin.Clm.Importer.Console.dll
    

    flock 似乎在运行时写入了一个锁文件,并执行了命令。它再次执行,并且锁定文件在那里,它只是失败了。完成后,它释放文件,允许再次调用它。

    简而言之:它充当信号量:)

    【讨论】:

      【解决方案3】:

      如果你想在你的程序中解决它,你可以遍历 System.Diagnostics.Process.GetProcesses 中的所有进程并检查是否有任何进程可执行路径以你的文件名结尾。

      [System.STAThread]
      static void Main(string[] args)
      {
          foreach (System.Diagnostics.Process p in System.Diagnostics.Process.GetProcesses())
          {
              if (p.MainModule.FileName.EndsWith("bla.exe", System.StringComparison.CurrentCultureIgnoreCase))
                  return;
          }
          [...]
      }
      

      否则,让脚本在 /var/run 中设置一个值并检查该文件是否存在。如果您的程序以任何方式退出,该文件将被删除。

      另外,解决长时间运行的问题。 程序通常不应该花费那么长时间。 在我看来你做错了什么,除非你确实在处理几 GB 的数据。

      如果 MainModule 返回 dotnet,您还可以读取链接 proc/pid/exe/(Linux 或 BSD)或 /proc/self/exe(仅限 Linux)

         int pid = System.Diagnostics.Process.GetCurrentProcess().Id;
         System.Text.StringBuilder sb = new System.Text.StringBuilder(System.Environment.SystemPageSize);
         int ret = Mono.Unix.Native.Syscall.readlink($"/proc/{pid}/exe", sb);
         string res = sb.ToString();
         System.Console.WriteLine(res);
      

      或者,如果这也只产生 dotnet,您可以读取命令行参数(/proc/pid/cmdline - 仅限 linux):

      public static byte[] ReadReallyAllBytes(string filename)
      {
          byte[] retValue = null;
      
          using (System.IO.FileStream fs = System.IO.File.OpenRead(filename))
          {
              byte[] buffer = new byte[System.Environment.SystemPageSize];
              List<byte> byteList = new List<byte>();
              int ct = 0;
              while ((ct = fs.Read(buffer, 0, buffer.Length)) > 0)
              {
                  for (int i = 0; i < ct; ++i)
                  {
                      byteList.Add(buffer[i]);
                  }
              }
      
              buffer = null;
              retValue = byteList.ToArray();
              byteList.Clear();
              byteList = null;
          }
      
          return retValue;
      }
      
      
      public static List<string> GetCmdLineArgs(int pid)            
      {
          List<string> ls = new List<string>();                
      
          byte[] buffer = ReadReallyAllBytes($"/proc/{pid}/cmdline");
          int last = 0;
      
          for (int i = 0; i < buffer.Length; ++i)
          {
              if (buffer[i] == 0)
              {
                  string arg = System.Text.Encoding.UTF8.GetString(buffer, last, i-last);
                  last = i + 1;
                  ls.Add(arg);
              } // End if (buffer[i] == 0)
      
          } // Next i 
      
          // System.Console.WriteLine(ls);
          return ls;
      }
      

      现在如果 MainModule 是 dotnet,请检查命令行参数列表是否包含您的 dll/exe。 此外,如果您进行发布构建(独立 - 无共享框架),那么它应该与 MainModule 一起使用。

      【讨论】:

      • 有时长时间运行的进程是由于全天进行多次检查而在检查之间暂停(IE 每小时检查一次结果是否处理数据)
      • @hellyale:由于 cron 调用您的进程,即每小时一次,因此程序完成后无需继续运行......否则您将不需要 cron。
      • 您的第一个解决方案将不起作用,因为 Process.MainModule.FileName 对于每个 NET Core 应用程序始终是“dotnet”。
      • @xneg:正确方式:readlink /proc//exe(Linux、BSD)或 readlink /proc/self/exe(仅限 Linux)
      • @StefanSteiger 你如何在代码中应用它。能举个例子吗?
      【解决方案4】:

      也许将重复检查放在脚本中,而不是在程序中。

      所以检查 'ps -ef | 的输出grep myprogramname' 在执行你的程序之前。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-07-17
        • 2010-10-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-16
        • 2020-04-29
        相关资源
        最近更新 更多