Share:

Контроль дубликатов процессов в C# (.NET)

YPermitinв.NET

2024-12-31

#C#

#.NET

#дубликат

#контроль

#процессы

Копия об оригинале: — это моя вторая натура!
(с) Владимир Михайлович Хочинский

Небольшая заметка по контролю запуска дубликатов процесса

Содержание

О чём речь?

Сегодня мы рассмотрим простейший пример решения простейшей задачи. При разработке приложений может понадобиться сделать защиту, чтобы приложение нельзя было запускать несколько раз.

Такое может понадобится, если параллельный запуск нескольких экземпляров процесса потенциально приводит к поломкам работы приложения или всей операционной системы (да, такое тоже бывает).

По сравнению с предыдущими темами, которые мы рассматривали в контексте управления процессами в C# / C++, эта тема довольно простая. Вот те самые предыдущие публикации:

Время начинать!

Контроль под Windows

Наш пример контроля под Windows будет в контексте работы с консольным приложением. Вот весь итоговый листинг кода с контролем запуска дубля приложения.

using System.Threading;
namespace ProcessControl
{
internal class Program
{
static void Main(string[] args)
{
// Создаем объект синхронизации типа мьютекс (mutex),
// с помощью которого можно контролировать доступ к ресурсам между
// потоками или отдельными процессами.
bool createdNew;
Mutex appMutex = new Mutex(
// Вызывающий поток должен быть владельцем мьютекса.
// То есть мы не сможем получить объект мьютекса, если он был создан
// другими процессами или потоками.
true,
// Уникальное имя мьютекса. Для примера используем имя приложения.
AppDomain.CurrentDomain.FriendlyName,
// Возвращает признак того, что предоставлено владением мьютексом.
out createdNew);
if (createdNew)
{
// Мьютекс был успешно создан и предоставлено право владения.
// То есть это первый уникальный запущенный процесс приложения.
Console.WriteLine("Это первый запуск приложения. Продолжаем работу...");
Console.WriteLine("Для выхода нажмите любую клавишу...");
Console.ReadKey();
// Освобождаем мьютекс
appMutex.ReleaseMutex();
}
else
{
// Создать и получить право владения мьютексом не удалось.
// Значит объект занят другими процессами.
Console.WriteLine("Это дубликат запущенного приложения. Прерываем работу...");
// Эмуляция работы по завершению приложения.
Thread.Sleep(5000);
// Завершаем процесс с кодом возврата 1, что говорит об ошибке.
Environment.Exit(1);
}
}
}
}

В целом все довольно просто. Создаем объект мьютекса (Mutex). Это объект ядра операционной системы, позволяющий синхронизировать доступ к ресурсами между потоками и даже между процессами. Последняя возможность нам как раз и нужна, ведь контроль работает между несколькими процессами.

Мы создаем объект мьютекса с указанем получения права владения. Это позволяет определить, что мы не получим мьютекс, созданный другими процессами. Если в итоге мы получаем флаг, что мьютекс был создан успешно, то это означает уникальный первый запуск приложения. В противном случае это дубликат и нам нужно завершить работу.

Но это работает только под Windows, т.к. поведение объектов Mutex в *.nix будет иным.

Контроль под *.nix

При решении той же задачи под *.nix поведение объектов Mutex будетм другим и приведенное выше решение уже не будет корректно работать.

Но есть решение и в этом случае, которое больше походит на настоящий *.nix-way. В Linux все это файлы, значит и объект синхронизации у нас тоже может быть файлом. Создадим вспомогательный класс LockfileMutex, с помощью которого будем устанавливать эксклюзивную блокировку файла. Именно это и будем нашим "мьютексом".

public class LockfileMutex : IDisposable {
private readonly string _fileName;
private FileStream? _stream;
public LockfileMutex(string name)
{
var assemblyDir = Path.GetDirectoryName(typeof(LockfileMutex).Assembly.Location) ?? throw new FileNotFoundException("cannot determine assembly location");
var file = Path.GetFullPath(Path.Combine(assemblyDir, name));
_fileName = file;
}
public bool Acquire() {
try {
_stream = new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
return true;
} catch (IOException ex) when (ex.Message.Contains(_fileName)) {
return false;
}
}
public void Dispose() {
if (_stream != null) {
_stream.Dispose();
try {
File.Delete(_fileName);
} catch {
// ignored
}
}
GC.SuppressFinalize(this);
}
}

Класс используется для создания файла блокировки (он же Lock-файл) с последующей установкой эксклюзивного доступа к нему. Пока прилоежние будет активно, эта блокировка файла будет сохраняться и не позволит его заблокировать другим процессам или потокам.

Ниже пример консольного прилоежния под *.nix, которое будет использовать описанный выше класс.

namespace ProcessControl;
class Program
{
static void Main(string[] args)
{
string controlFileName = $"{AppDomain.CurrentDomain.FriendlyName}Lock";
using (var mutex = new LockfileMutex(controlFileName))
{
if (mutex.Acquire())
{
// Мьютекс был успешно создан и предоставлено право владения.
// То есть это первый уникальный запущенный процесс приложения.
Console.WriteLine("Это первый запуск приложения. Продолжаем работу...");
Console.WriteLine("Для выхода нажмите любую клавишу...");
Console.ReadKey();
}
else
{
// Создать и получить право владения мьютексом не удалось.
// Значит, объект занят другими процессами.
Console.WriteLine("Это дубликат заппущенного приложения. Прерываем работу...");
// Эмуляция работы по завершению приложения.
Thread.Sleep(5000);
// Завершаем процесс с кодом возврата 1, что говорит об ошибке.
Environment.Exit(1);
}
}
}
public class LockfileMutex : IDisposable {
private readonly string _fileName;
private FileStream? _stream;
public LockfileMutex(string name) {
var assemblyDir = Path.GetDirectoryName(typeof(LockfileMutex).Assembly.Location) ?? throw new FileNotFoundException("cannot determine assembly location");
var file = Path.GetFullPath(Path.Combine(assemblyDir, name));
_fileName = file;
}
public bool Acquire() {
try {
_stream = new FileStream(_fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
return true;
} catch (IOException ex) when (ex.Message.Contains(_fileName)) {
return false;
}
}
public void Dispose() {
if (_stream != null) {
_stream.Dispose();
try {
File.Delete(_fileName);
} catch {
// ignored
}
}
GC.SuppressFinalize(this);
}
}
}

В целом этот подход будет работать и под Windows, но там у нас есть объект ядра Mutex. А если нет разницы, то зачем городить костыли?

Альтернативы

На самом деле есть множество других способов контроля дубликата запущенного процесса. Мы лишь рассмотрели самые простые и надежные.

К альтернативным способам контроля можно отнести:

  • Проверка списка запущенных процессов и поиск в нем дубликата процессов по имени.
  • Сохранение файла во временный каталог и проверка его наличия или его блокировка. В целом этот способ чем-то похож на предложенный выше путь для Linux, но реализуется проще.
  • Поиск существующего процесса по открытым сетевым портам для прослушивания.
  • Проверка служебных записей в базе данных приложения, если такая присутствует.
  • И многие другие.

Все как обычно зависит от конкретной задачи. Ознакомлен - значит вооружен!

Вот и всё

В этой министатье мы рассмотрели способы контроля дубликата запущенного приложения (и его процесса) двумя самыми распостраненными способами.

Первый был под Windows и использовал объект ядра мьютекс (mutex), а второй больше подходил под *.nix системы и использовал файл блокировки.

Конечно, это лишь поверхностное освещение темы. Есть множество альтернативных способов решения той же задачи. В многообразии сила!

Спасибо за интерес к теме и удачи в делах!

А самое главное - с наступающим Новым 2025 Годом!

P.S. Если же вы читаете это уже в наступившем Новом году, то с Новым Годом!

Y

YPermitin

.NET, TSQL, DevOps, 1C:Enterprise

Developer, just developer.

Поделиться

Другие статьи

Контроль дубликатов процессов в C# (.NET)
Контроль дубликатов процессов в C# (.NET)
Контроль дочерних процессов с помощью C++ и WinAPI
Контроль дочерних процессов с помощью C++ и WinAPI
Расширение для SQL Server. Быстро и просто. SQLCLR снова в деле
Расширение для SQL Server. Быстро и просто. SQLCLR снова в деле

Все статьи от автора: YPermitin

Copyright © 2024 Убежище инженера