C# linux上的单实例dotnetcore cli应用程序
我感兴趣的是如何为dotnetcore控制台应用程序提供单实例策略。令我惊讶的是,似乎没有太多关于这个话题的内容。我找到了这个stacko,但它似乎不适合我使用ubuntu的dotnetcore。这里有人曾经这样做过吗?关于@MusuNaji解决方案的变体,网址:C# linux上的单实例dotnetcore cli应用程序,c#,ubuntu,.net-core,C#,Ubuntu,.net Core,我感兴趣的是如何为dotnetcore控制台应用程序提供单实例策略。令我惊讶的是,似乎没有太多关于这个话题的内容。我找到了这个stacko,但它似乎不适合我使用ubuntu的dotnetcore。这里有人曾经这样做过吗?关于@MusuNaji解决方案的变体,网址: 由于Linux/MacOS上的互斥体检查问题(如上所述),这在.NET core上比应该的要困难一些。此外,Youth的解决方案也没有帮助,因为所有.NET core应用程序都是通过CLI运行的,CLI的进程名为“dotnet”,如果
由于Linux/MacOS上的互斥体检查问题(如上所述),这在.NET core上比应该的要困难一些。此外,Youth的解决方案也没有帮助,因为所有.NET core应用程序都是通过CLI运行的,CLI的进程名为“dotnet”,如果在同一台机器上运行多个.NET core应用程序,则会错误触发重复实例检查 实现这一点的一种简单方法也是多平台健壮的,即在应用程序启动时打开一个文件进行写入,并在最后关闭它。如果文件无法打开,则是由于另一个实例同时运行,您可以在try/catch中处理该问题。如果文件不存在,使用FileStream打开文件也会创建它
try
{
lockFile = File.OpenWrite("SingleInstance.lck");
}
catch (Exception)
{
Console.WriteLine("ERROR - Server is already running. End that instance before re-running. Exiting in 5 seconds...");
System.Threading.Thread.Sleep(5000);
return;
}
下面是我使用命名管道的实现。它支持从第二个实例传递参数 注意:我没有在Linux或Mac上进行测试,但理论上应该可以 用法
public static int Main(string[] args)
{
instanceManager = new SingleInstanceManager("8A3B7DE2-6AB4-4983-BBC0-DF985AB56703");
if (!instanceManager.Start())
{
return 0; // exit, if same app is running
}
instanceManager.SecondInstanceLaunched += InstanceManager_SecondInstanceLaunched;
// Initialize app. Below is an example in WPF.
app = new App();
app.InitializeComponent();
return app.Run();
}
private static void InstanceManager_SecondInstanceLaunched(object sender, SecondInstanceLaunchedEventArgs e)
{
app.Dispatcher.Invoke(() => new MainWindow().Show());
}
public class SingleInstanceManager
{
private readonly string applicationId;
public SingleInstanceManager(string applicationId)
{
this.applicationId = applicationId;
}
/// <summary>
/// Detect if this is the first instance. If it is, start a named pipe server to listen for subsequent instances. Otherwise, send <see cref="Environment.GetCommandLineArgs()"/> to the first instance.
/// </summary>
/// <returns>True if this is tthe first instance. Otherwise, false.</returns>
public bool Start()
{
using var client = new NamedPipeClientStream(applicationId);
try
{
client.Connect(0);
}
catch (TimeoutException)
{
Task.Run(() => StartListeningServer());
return true;
}
var args = Environment.GetCommandLineArgs();
using (var writer = new BinaryWriter(client, Encoding.UTF8))
{
writer.Write(args.Length);
for (int i = 0; i < args.Length; i++)
{
writer.Write(args[i]);
}
}
return false;
}
private void StartListeningServer()
{
var server = new NamedPipeServerStream(applicationId);
server.WaitForConnection();
using (var reader = new BinaryReader(server, Encoding.UTF8))
{
var argc = reader.ReadInt32();
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = reader.ReadString();
}
SecondInstanceLaunched?.Invoke(this, new SecondInstanceLaunchedEventArgs { Arguments = args });
}
StartListeningServer();
}
public event EventHandler<SecondInstanceLaunchedEventArgs> SecondInstanceLaunched;
}
public class SecondInstanceLaunchedEventArgs
{
public string[] Arguments { get; set; }
}
您的复制和粘贴代码
public static int Main(string[] args)
{
instanceManager = new SingleInstanceManager("8A3B7DE2-6AB4-4983-BBC0-DF985AB56703");
if (!instanceManager.Start())
{
return 0; // exit, if same app is running
}
instanceManager.SecondInstanceLaunched += InstanceManager_SecondInstanceLaunched;
// Initialize app. Below is an example in WPF.
app = new App();
app.InitializeComponent();
return app.Run();
}
private static void InstanceManager_SecondInstanceLaunched(object sender, SecondInstanceLaunchedEventArgs e)
{
app.Dispatcher.Invoke(() => new MainWindow().Show());
}
public class SingleInstanceManager
{
private readonly string applicationId;
public SingleInstanceManager(string applicationId)
{
this.applicationId = applicationId;
}
/// <summary>
/// Detect if this is the first instance. If it is, start a named pipe server to listen for subsequent instances. Otherwise, send <see cref="Environment.GetCommandLineArgs()"/> to the first instance.
/// </summary>
/// <returns>True if this is tthe first instance. Otherwise, false.</returns>
public bool Start()
{
using var client = new NamedPipeClientStream(applicationId);
try
{
client.Connect(0);
}
catch (TimeoutException)
{
Task.Run(() => StartListeningServer());
return true;
}
var args = Environment.GetCommandLineArgs();
using (var writer = new BinaryWriter(client, Encoding.UTF8))
{
writer.Write(args.Length);
for (int i = 0; i < args.Length; i++)
{
writer.Write(args[i]);
}
}
return false;
}
private void StartListeningServer()
{
var server = new NamedPipeServerStream(applicationId);
server.WaitForConnection();
using (var reader = new BinaryReader(server, Encoding.UTF8))
{
var argc = reader.ReadInt32();
var args = new string[argc];
for (int i = 0; i < argc; i++)
{
args[i] = reader.ReadString();
}
SecondInstanceLaunched?.Invoke(this, new SecondInstanceLaunchedEventArgs { Arguments = args });
}
StartListeningServer();
}
public event EventHandler<SecondInstanceLaunchedEventArgs> SecondInstanceLaunched;
}
public class SecondInstanceLaunchedEventArgs
{
public string[] Arguments { get; set; }
}
似乎在macOS上使用命名互斥还不够(只是测试了一下)。您可以尝试使用某种类型的pidfile,只需确保在主进程退出时始终删除该文件。是的,我以前想过,但我希望有更好的方法。现在我想了想,如果您的进程名称不唯一,这将无法正常工作。因此,这是这个解决方案的先决条件。仍然可以使用100%可靠的方法来增强单实例策略。我想在我的例子中,实现单实例策略的最佳方法是将其设置为linux守护进程。我认为至少在upstart中,单实例是我的默认值。我不认为这会很好地工作,因为所有的.net core进程名称都是“netcore”(无论如何在2.x中),这是CLI,而不是您的特定应用程序名称,这意味着任何.NET core应用程序都将触发对进程名称的测试。更正dotnet core进程名称为dotnet而不是netcore。请参阅上面的我的答案,以获得一个简单的替代方案,该方案应该会更好。您断言所有netcore应用程序都是通过dotnet CLI运行的,这是不正确的,尽管您指出从CLI运行不会正确地与我的解决方案配合使用,这是好事。构建自包含应用程序并在dotnet CLI之外执行该应用程序时,它与可执行文件具有相同的名称。如果应用程序在不关闭流的情况下崩溃,会发生什么情况?它能保持打开状态吗?是的,我正在通过Visual Studio进行测试,如果使用自包含的应用程序运行,您关于名称更改的说法是正确的。此外,崩溃应用程序Windows将关闭流(测试正常),但尚未在Linux上尝试此操作。