I am interested in how to inforce a single instance policy for dotnetcore console apps. To my surprise it seems like there isn't much out there on the topic. I found this one stacko, How to restrict a program to a single instance, but it doesnt seem to work for me on dotnetcore with ubuntu. Anyone here do this before?
-
It seems like using a named mutex is not enough on macOS as well (just tested it). You could try using some sort of pidfile, just need to ensure that the file is always deleted when the main process exits. – dbattaglia Jun 15 '17 at 19:56
-
1Yea I thought of that before but I am hoping for a better way. – BillHaggerty Jun 19 '17 at 11:30
4 Answers
Variation of @MusuNaji's solution at: How to restrict a program to a single instance
private static bool AlreadyRunning()
{
Process[] processes = Process.GetProcesses();
Process currentProc = Process.GetCurrentProcess();
logger.LogDebug("Current proccess: {0}", currentProc.ProcessName);
foreach (Process process in processes)
{
if (currentProc.ProcessName == process.ProcessName && currentProc.Id != process.Id)
{
logger.LogInformation("Another instance of this process is already running: {pid}", process.Id);
return true;
}
}
return false;
}

- 6,157
- 10
- 35
- 68
-
Now that I think about it, this wouldn't work work well if your process name isn't unique. So that is a prerequisite for this solution. Still open to a 100% reliable way of inforcing a single instance policy. – BillHaggerty Jun 23 '17 at 13:41
-
I guess the best way to achieve a single instance policy in my case would be to make it a linux daemon. I think with upstart at least, single instance is enforced my default. – BillHaggerty Sep 08 '17 at 11:42
-
1I don't think this will work well as all .net core process names are 'netcore' (in 2.x anyway) which is the CLI rather than your specific application name, which means any .NET core app will trigger the test for process name. – deandob Mar 28 '18 at 05:49
-
1Correction the dotnet core process name is dotnet not netcore. See my answer above for a simple alternative that should work better. – deandob Mar 28 '18 at 06:18
This is a little more difficult on .NET core than it should be, due to the problem of mutex checking on Linux/MacOS (as reported above). Also Theyouthis's solution isn't helpful as all .NET core apps are run via the CLI which has a process name of 'dotnet' which if you are running multiple .NET core apps on the same machine the duplicate instance check will trigger incorrectly.
A simple way to do this that is also multi-platform robust is to open a file for write when the application starts, and close it at the end. If the file fails to open it is due to another instance running concurrently and you can handle that in the try/catch. Using FileStream to open the file will also create it if it doesn't first exist.
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;
}

- 5,100
- 5
- 25
- 37
-
Your assertion that all netcore apps are run via the dotnet CLI is incorrect, although its good that you pointed out that running from the CLI will not work correctly with my solution. When you build a self contained app and execute the application outside of the dotnet CLI it has the same name as the executable. What happens if the app crashes without closing the stream, can it stay open? – BillHaggerty Mar 28 '18 at 12:29
-
Yes, I was testing via Visual Studio and you are correct about the name changing if running with a self contained app. Also, crashing the app Windows will close the stream (tested OK) but have not tried this on Linux. – deandob Mar 29 '18 at 01:47
Here is my implementation using Named pipes. It supports passing arguments from the second instance.
Note: I did not test on Linux or Mac but it should work in theory.
Usage
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());
}
Your Copy-and-paste code
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; }
}
Unit test
[TestClass]
public class SingleInstanceManagerTests
{
[TestMethod]
public void SingleInstanceManagerTest()
{
var id = Guid.NewGuid().ToString();
var manager = new SingleInstanceManager(id);
string[] receivedArguments = null;
var correctArgCount = Environment.GetCommandLineArgs().Length;
manager.SecondInstanceLaunched += (sender, e) => receivedArguments = e.Arguments;
var instance1 = manager.Start();
Thread.Sleep(200);
var manager2 = new SingleInstanceManager(id);
Assert.IsFalse(manager2.Start());
Thread.Sleep(200);
Assert.IsTrue(instance1);
Assert.IsNotNull(receivedArguments);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
var receivedArguments2 = receivedArguments;
var manager3 = new SingleInstanceManager(id);
Thread.Sleep(200);
Assert.IsFalse(manager3.Start());
Assert.AreNotSame(receivedArguments, receivedArguments2);
Assert.AreEqual(correctArgCount, receivedArguments.Length);
}
}

- 1,518
- 18
- 19
-
This is not atomic solution. It is still possible to run multiple instances before other application starts listening to the first instance. When I was testing it starting 1000 instances at once. 500 of them were able to start before other found that some instances are already running. – Michał Jankowski Oct 11 '21 at 12:51
-
@MichałJankowski Indeed.. In my case, it was only to prevent a human from launching multiple instances. It can probably be modified to wait for `StartListeningServer` to complete, and check if the pipe server was created successfully. Feel free to modify the answer if you do go down this route :) – fjch1997 Oct 11 '21 at 18:21
The downside of deandob's solution is that one can launch the application from another path. So you may prefer some static path or a tmp path for all users.
Here is my attempt:
//second instance launch guard
var tempPath = Environment.GetEnvironmentVariable("TEMP", EnvironmentVariableTarget.Machine)
??
Path.GetTempPath();
var lockPath = Path.Combine(tempPath, "SingleInstance.lock");
await using var lockFile = File.OpenWrite(lockPath);
here I'm trying to get TEMP
system variable at the scope of machine (not the user TEMP
) and if its empty - fallback to the user's temp folder on windows or shared /tmp
on some linuxes.

- 674
- 3
- 11