Можно к примеру попробовать создать Mutex указав его имя.
Если удалось открыть - значит ещё не запущен второй экземпляр.
Если не удалось открыть - значит уже запущен.
Это по идее должно быть надёжнее, чем создавать файл.
Вот рабочий код:
using var globalMutex = new Mutex(false, "Global\\test_mx");
var result = globalMutex.WaitOne(1000);
Console.WriteLine(result ? "Owned" : "Not owned in 1s");
Console.ReadLine();
При попытке запустить сразу два экземпляра, в одном будет выведено:
Owned
, а в другом
Not owned in 1s
Префикс
Global\
обязателен. Без него мутекс будет локальный для текущего процесса.