Как правильно создать приложение WPF с одним экземпляром?

Как правильно использовать C # и WPF под .NET (а не Windows Forms или консоль) создать приложение, которое можно запускать только как единичный экземпляр?

Я знаю, что это имеет какое-то отношение к какой-то мифической вещи, называемой мьютексом, я редко могу найти кого-то, кто потрудится остановиться и объяснить, что это за мьютекс.

Код должен также сообщить уже запущенному экземпляру, что пользователь попытался запустить второй, и, возможно, также передать любые аргументы командной строки, если таковые существуют.


person Nidonocu    schedule 21.08.2008    source источник
comment
Разве CLR не освобождает автоматически невыпущенные мьютексы, когда приложение все равно завершает работу?   -  person Cocowalla    schedule 31.10.2009
comment
@Cocowalla: финализатор должен избавляться от неуправляемых мьютексов, если он не может знать, был ли мьютекс создан управляемым приложением или прикреплен к существующему.   -  person Ignacio Soler Garcia    schedule 30.05.2012
comment
Разумно иметь только один экземпляр вашего приложения. Но передача аргументов в уже существующее приложение кажется мне немного глупой. Я не вижу причин для этого. Если вы связываете приложение с расширением файла, вы должны открыть столько приложений, сколько пользователь хочет открыть документов. Это стандартное поведение, которого ожидают все пользователи.   -  person Eric Ouellet    schedule 06.12.2013
comment
Просто хочу исправить свое предыдущее состояние. Передача аргументов в существующее приложение означает, что вы хотите использовать MDI (многодокументный интерфейс). Я думал, что MDI был тем способом, который Microsoft продвигала (Word и Excel теперь SDI). Но я понимаю, что и Chrome, и IE являются MDI. Perharps мы живем в годы, когда MDI вернулся ??? (Но я по-прежнему предпочитаю SDI, а не MDI)   -  person Eric Ouellet    schedule 09.12.2013
comment
@Cocowalla CLR не управляет собственными ресурсами. Однако, если процесс завершается, все дескрипторы освобождаются системой (ОС, а не CLR).   -  person IInspectable    schedule 01.01.2014
comment
Я предпочитаю ответ @huseyint. Он использует собственный класс Microsoft SingleInstance.cs, поэтому вам не нужно беспокоиться о мьютексах и IntPtrs. Кроме того, нет зависимости от VisualBasic (yuk). См. codereview.stackexchange.com/ questions / 20871 / подробнее ...   -  person Riegardt Steyn    schedule 26.02.2014
comment
Я использую NuGet SingleInstanceApp. nuget.org/packages/SingleInstanceApp Прекрасно работает. Не требует ссылки на Microsoft.VisualBasic. Не зависит от версии приложения (в Microsoft.VisualBasic). Приложение идентифицируется только по уникальной строке.   -  person Boogier    schedule 07.02.2019


Ответы (38)


Вот очень хорошая статья о решении Mutex. Подход, описанный в статье, выгоден по двум причинам.

Во-первых, он не требует зависимости от сборки Microsoft.VisualBasic. Если бы у моего проекта уже была зависимость от этой сборки, я бы, вероятно, рекомендовал использовать подход, показанный в другом ответе. Но пока что я не использую сборку Microsoft.VisualBasic и не хочу добавлять в свой проект ненужную зависимость.

Во-вторых, в статье показано, как вывести существующий экземпляр приложения на передний план, когда пользователь пытается запустить другой экземпляр. Это очень приятный момент, который не учитываются в других описанных здесь решениях Mutex.


ОБНОВИТЬ

По состоянию на 01.08.2014 статья, на которую я ссылался выше, все еще активна, но блог не обновлялся некоторое время. Это заставляет меня беспокоиться о том, что в конечном итоге он может исчезнуть, а вместе с ним и предлагаемое решение. Я воспроизводю здесь содержание статьи для потомков. Слова принадлежат исключительно владельцу блога Sanity Free Coding.

Сегодня я хотел провести рефакторинг кода, который запрещал моему приложению запускать несколько экземпляров самого себя.

Раньше я использовал System.Diagnostics. Процесс для поиска экземпляра myapp.exe в списке процессов. Хотя это работает, это вызывает много накладных расходов, и я хотел что-то более чистое.

Зная, что я могу использовать для этого мьютекс (но никогда не делал этого раньше), я решил сократить свой код и упростить себе жизнь.

В классе основного приложения я создал статический объект с именем Мьютекс:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    ...
}

Именованный мьютекс позволяет нам осуществлять синхронизацию нескольких потоков и процессов, что является той магией, которую я ищу.

Mutex.WaitOne содержит перегрузку, которая указывает у нас есть время подождать. Поскольку на самом деле мы не хотим синхронизировать наш код (просто проверьте, используется ли он в настоящее время), мы используем перегрузку с двумя параметрами: Mutex.WaitOne (Timespan timeout, bool exitContext). Wait one возвращает true, если он может войти, и false, если нет. В этом случае мы вообще не хотим ждать; Если наш мьютекс используется, пропустите его и двигайтесь дальше, поэтому мы передаем TimeSpan.Zero (ждем 0 миллисекунд) и устанавливаем для exitContext значение true, чтобы мы могли выйти из контекста синхронизации, прежде чем пытаться получить на нем блокировку. Используя это, мы оборачиваем наш код Application.Run примерно так:

static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            MessageBox.Show("only one instance at a time");
        }
    }
}

Итак, если наше приложение запущено, WaitOne вернет false, и мы получим окно сообщения.

Вместо того, чтобы показывать окно сообщения, я решил использовать небольшой Win32, чтобы уведомить мой работающий экземпляр о том, что кто-то забыл, что он уже запущен (подняв себя вверху всех других окон). Для этого я использовал PostMessage для трансляции настраиваемого сообщения в каждое окно (настраиваемое сообщение было зарегистрировано с помощью RegisterWindowMessage моим работающим приложением, что означает, что только мое приложение знает, что это такое), то мой второй экземпляр завершает работу. Запускаемый экземпляр приложения получит это уведомление и обработает его. Для этого я переопределил WndProc в моей основной форме и прослушал мое настраиваемое уведомление. Когда я получил это уведомление, я установил для свойства TopMost значение true, чтобы оно отображалось наверху.

Вот что у меня получилось:

  • Program.cs
static class Program
{
    static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
    [STAThread]
    static void Main() {
        if(mutex.WaitOne(TimeSpan.Zero, true)) {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
            mutex.ReleaseMutex();
        } else {
            // send our Win32 message to make the currently running instance
            // jump on top of all the other windows
            NativeMethods.PostMessage(
                (IntPtr)NativeMethods.HWND_BROADCAST,
                NativeMethods.WM_SHOWME,
                IntPtr.Zero,
                IntPtr.Zero);
        }
    }
}
  • NativeMethods.cs
// this class just wraps some Win32 stuff that we're going to use
internal class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
  • Form1.cs (частичная лицевая сторона)
public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }
    protected override void WndProc(ref Message m)
    {
        if(m.Msg == NativeMethods.WM_SHOWME) {
            ShowMe();
        }
        base.WndProc(ref m);
    }
    private void ShowMe()
    {
        if(WindowState == FormWindowState.Minimized) {
            WindowState = FormWindowState.Normal;
        }
        // get our current "TopMost" value (ours will always be false though)
        bool top = TopMost;
        // make our form jump to the top of everything
        TopMost = true;
        // set it back to whatever it was
        TopMost = top;
    }
}
person Matt Davis    schedule 07.02.2009
comment
Исходя из того, что в этом ответе используется меньше кода и меньше библиотек и обеспечивается повышение функциональности, я собираюсь сделать это новым принятым ответом. Если кто-то знает более правильный способ вывести форму на вершину с помощью API, не стесняйтесь добавлять его. - person Nidonocu; 09.02.2009
comment
Не уверен, что понимаю - зачем использовать собственные сообщения? Вот для чего нужны события ... (если это для развязки, вам действительно следует использовать cab или EventBroker ...) - person BlueRaja - Danny Pflughoeft; 29.05.2010
comment
@BlueRaja, вы запускаете первый экземпляр приложения. Когда вы запускаете второй экземпляр приложения, он обнаруживает, что другой экземпляр уже запущен, и готовится к завершению работы. Перед этим он отправляет собственное сообщение SHOWME первому экземпляру, который выводит первый экземпляр наверх. События в .NET не допускают межпроцессного взаимодействия, поэтому используется собственное сообщение. - person Matt Davis; 29.05.2010
comment
Может быть, есть способ передать командные строки из другого экземпляра? - person gyurisc; 01.06.2010
comment
@matt david - не беспокойтесь о «доставке» Microsoft.VisualBasic - он уже находится в GAC. stackoverflow.com/ questions / 226517 / - person Simon_Weaver; 28.09.2010
comment
@Simon_Weaver, проблема не в том, находится он в GAC или нет, а в том, что он поставляется с .NET Framework. Я не знал об этом. Спасибо за ссылку. - person Matt Davis; 28.09.2010
comment
@matt - я просто хочу, чтобы они назвали эту чертову штуку как-нибудь иначе! Я только начал использовать этот компонент, и он работает довольно хорошо (мои пользователи никогда не узнают, что я опустился так низко, чтобы включить пространство имен VB - хех). очень удобно иметь возможность передавать параметры и / или выводить бездействующее приложение на передний план без необходимости самостоятельно вмешиваться в какой-либо коммуникационный код. - person Simon_Weaver; 28.09.2010
comment
@Matt: Как мы можем выбрать имя для Mutex? В примере это {8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F} Откуда это :)? - person Nam G VU; 19.10.2010
comment
@Nam, конструктору Mutex просто требуется строка, поэтому вы можете указать любое имя строки, которое хотите, например, This Is My Mutex. Поскольку «Mutex» - это системный объект, доступный для других процессов, вы обычно хотите, чтобы имя было уникальным, чтобы оно не конфликтовало с другими именами «Mutex» в той же системе. В статье строка, которая выглядит загадочно, называется Guid. Вы можете сгенерировать это программно, вызвав System.Guid.NewGuid(). В случае статьи пользователь, вероятно, сгенерировал ее через Visual Studio, как показано здесь: msdn.microsoft.com/en-us/library/ms241442 (VS.80) .aspx - person Matt Davis; 19.10.2010
comment
@Matt Davis, вы НЕ МОЖЕТЕ использовать System.Guid.NewGuid (), потому что каждый раз, когда вы получаете другой GUID, и, по сути, каждый экземпляр вашей программы будет утверждать, что это единственный экземпляр. Вы должны использовать некоторый токен, общий для ВСЕХ экземпляров, определенный во время компиляции. Сгенерированный VS GUID - это способ. - person greenoldman; 13.08.2011
comment
@macias, я никогда не говорил, что вы вызовете System.Guid.NewGuid(), а затем передадите результат в Mutex. Очевидно, что это было бы неправильно, как вы ловко заметили. Вопрос был в том, откуда взялась струна. Я просто говорил, что GUID можно сгенерировать программно или с помощью Visual Studio. - person Matt Davis; 13.08.2011
comment
Предполагает ли подход мьютекса, что тот же пользователь пытается снова запустить приложение? Конечно, вывод существующего экземпляра приложения на передний план не имеет смысла после «смены пользователя». - person dumbledad; 07.08.2012
comment
Вопрос задан для подхода к WPF, а не к WinForms, как предлагается в ответе. Я использовал этот ответ для приложения WPF. Он также размещен здесь как ответ: stackoverflow.com/a/2932076/433718 - person OneWorld; 11.02.2013
comment
@OneWorld, это решение не зависит от инфраструктуры пользовательского интерфейса. В примере показано, как это сделать с помощью WinForms, но это было бы так же просто использовать в WPF. В опубликованном вами решении используется .NET Remoting, устаревшая технология, которую Microsoft сохраняет только для обратной совместимости. Вряд ли такое решение я бы поддержал. msdn.microsoft.com/en-us/library /kwdt6w2k(v=vs.100).aspx - person Matt Davis; 11.02.2013
comment
@MattDavis Не знал об этом. Хорошо, что мы это прояснили. Автор Арик Познанский, похоже, выразил большую уверенность в том, что его подход сделал все правильно. И несколько человек сказали, что это подход для новичков, так как он был с 2010 года. Я не использовал ваше решение, потому что не смог найти метод protected override void WndProc(ref Message m) - person OneWorld; 11.02.2013
comment
@OneWorld, ответы смотрите здесь. stackoverflow.com/questions/624367 / - person Matt Davis; 11.02.2013
comment
Хотя этот пост старый, ответ на @gyurisc может помочь будущим исследователям. Этот ответ хорошо подходит для реализации приложения с одним экземпляром. У нас есть подобное решение, которое какое-то время отлично работает с нашей клиентской базой. Чтобы передать аргументы уже запущенному экземпляру, мы можем просто записать аргументы в файл и поместить его в AppData для приложения. Который можно подобрать, запустив app. Имя файла может быть DatetTime.Ticks, чтобы на всякий случай были получены самые последние аргументы! - person Sameer Vartak; 22.06.2015
comment
По какой-то причине, если вы установите this.ShowInTaskbar = false, этот метод не будет работать. Он просто не получит сообщение WM_SHOWME в методе WndProc. - person BornToCode; 18.11.2015
comment
Вместо того, чтобы устанавливать TopMost туда и обратно, можно просто вызвать Activate(); - person Matyas; 22.06.2016
comment
Вместо использования старой системы собственных сообщений вы можете использовать NamedPipe с NamedPipeClientStream и NamePipeServerStream. Он более современный и позволяет передавать любую дополнительную информацию (например, сообщение, требующее завершения работы живого экземпляра). Я не знаю, какова стоимость этого метода, но я использовал его, не замечая проблем. Для моего использования я просто вызываю npServer.WaitForConnection(), а затем немедленно закрываю поток, чтобы создать новый NamedPipeServerStream и ждать других уведомлений. Это в бесконечном цикле. - person Emrys Myrooin; 07.07.2016
comment
В этом ответе не описывается, как передать аргументы командной строки другому экземпляру. Это не должен быть приемлемый ответ. - person phobos2077; 14.10.2016
comment
Как на этот код влияют несколько пользователей, запускающих одно и то же приложение в разных сеансах? Код на сеанс? Потому что я хочу ограничить только одно приложение на сеанс, а не весь компьютер. - person serializer; 25.01.2017
comment
@serializer Чтобы ограничить одно приложение на весь компьютер, вам нужен префикс Global \ для имени мьютекса. См. msdn.microsoft.com/en-us/library/system .threading.mutex.aspx - person Serg; 25.01.2017
comment
@Serg Я думаю, что искал Local \, так как хочу ограничить сеанс, а не компьютер. Спасибо, в любом случае! - person serializer; 27.01.2017
comment
Этот ответ в сочетании с stackoverflow.com/a/9330103/2358659 дает мне действительно отличное решение для приложения WPF, как я не мог ' Я не могу разобраться в WndProc с WPF. - person Maz T; 22.02.2017
comment
Подумайте о том, чтобы поймать AbandonedMutexException при вызове WaitOne. Это будет означать, что предыдущий экземпляр приложения был внезапно завершен без освобождения мьютекса. Вам, вероятно, все равно, поскольку он в настоящее время больше не работает и вы можете запустить новый экземпляр. - person Malgaur; 22.07.2017
comment
Это будет использовать GUID сборки. static Mutex mutex = new Mutex(true, ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), true)[0]).Value); Также вы можете просто сделать это в своей основной форме, и не нужно освобождать мьютекс, финализатор сделает это за вас, когда программа завершится. - person Liam Mitchell; 18.01.2018
comment
Не могли бы вы добавить примечание о мьютексе? Похоже, что более 120 программ сейчас используют один и тот же Mutex: github. ru / - person dustytrash; 18.02.2020
comment
@dustytrash lol. Не имел представления. В приведенном выше решении используется так называемый именованный мьютекс, что делает его доступным на системном уровне. Это важно, чтобы первый процесс и все последующие процессы могли получить к нему доступ. Единственное требование - это должна быть строка. В приведенном выше примере я создал Guid с помощью генератора Guid Visual Studio (Tools | Create Guid), чтобы строка была уникальной для приложения. Вы можете сгенерировать и использовать другой Guid или использовать собственную уникальную строку. Вы просто не хотите, чтобы это приводило к конфликтам с другими именованными мьютексами в системе. HTH - person Matt Davis; 18.02.2020
comment
@MattDavis Проблема в том, что люди копируют и вставляют ваш Mutex, не понимая этого. Любой, кто использует этот код, должен сгенерировать свой собственный Mutex так же, как и вы, таким образом он уникален и не используется в других программах в той же системе. - person dustytrash; 18.02.2020
comment
@MattDavis Может ли ответ генерировать случайную строку? - person TZubiri; 19.02.2020
comment
@Tomas Zubiri Насколько я понимаю ваш вопрос, нет. При открытии каждого экземпляра приложения он должен иметь возможность найти мьютекс, созданный самым первым экземпляром приложения. Таким образом, чтобы это решение работало, необходимо указать имя. Если каждый экземпляр генерирует случайную строку, а затем ищет мьютекс в этой случайной строке, каждый экземпляр приложения создает новый мьютекс, и все они остаются открытыми. Теперь, если вы имели в виду, может ли имя Mutex быть случайной строкой, а не строкой на основе Guid, да. Вы можете использовать, например, MyRandomlyNamedMutex. - person Matt Davis; 19.02.2020
comment
А, я понимаю, может быть, к строке может быть добавлена ​​строка для конкретного проекта? Вроде как пространство имен. - person TZubiri; 19.02.2020
comment
@Tomas Zubiri Это может быть любая строка, какой захотите. Его просто нужно исправить, чтобы каждый экземпляр приложения мог найти именованный Mutex. Если строка меняется от экземпляра приложения к экземпляру приложения, это решение не будет работать, потому что каждый экземпляр приложения будет искать уникальный мьютекс, в котором решение основано на одном мьютексе для всех экземпляров приложения. - person Matt Davis; 19.02.2020
comment
Ага, я понял. Я предполагал, что в ответ можно было бы предварительно перенести на него конкретную строку проекта, чтобы избежать ошибок копирования и вставки. - person TZubiri; 19.02.2020
comment
@TomasZubiri Если люди копируют и вставляют код из SO, не читая и не понимая его, это их проблема, а не Мэтта. - person Charles Boyung; 15.10.2020
comment
Как уже упоминал Малгаур, этот ответ бросает AbandonedMutexException на WaitOne. Ответ Legends также использует Mutex, но не генерирует исключение. - person Martin Schneider; 30.03.2021
comment
В приложениях WPF вы можете решить эту проблему без использования waitone (который может вызвать исключение). См .: stackoverflow.com/a/59079638/4491768 - person Wouter; 18.05.2021
comment
защищенное переопределение void WndProc (ref Message m) работает только в режиме запуска от имени администратора - person ismail uzunok; 21.05.2021

Вы можете использовать класс Mutex, но вскоре вы обнаружите, что вам нужно будет реализовать код для передачи аргументов и тому подобного самостоятельно. Что ж, я научился трюку при программировании в WinForms, когда прочитал книгу Криса Селла. Этот трюк использует логику, которая уже доступна нам во фреймворке. Не знаю, как вы, но когда я узнаю о вещах, которые могу повторно использовать во фреймворке, я обычно иду по этому пути вместо того, чтобы изобретать колесо. Если, конечно, не все, что я хочу.

Когда я вошел в WPF, я придумал способ использовать тот же код, но в приложении WPF. Это решение должно соответствовать вашим потребностям, исходя из вашего вопроса.

Во-первых, нам нужно создать наш класс приложения. В этом классе мы переопределим событие OnStartup и создадим метод под названием Activate, который будет использоваться позже.

public class SingleInstanceApplication : System.Windows.Application
{
    protected override void OnStartup(System.Windows.StartupEventArgs e)
    {
        // Call the OnStartup event on our base class
        base.OnStartup(e);

        // Create our MainWindow and show it
        MainWindow window = new MainWindow();
        window.Show();
    }

    public void Activate()
    {
        // Reactivate the main window
        MainWindow.Activate();
    }
}

Во-вторых, нам нужно будет создать класс, который сможет управлять нашими экземплярами. Прежде чем мы пройдем через это, мы на самом деле собираемся повторно использовать некоторый код, который находится в сборке Microsoft.VisualBasic. Поскольку в этом примере я использую C #, мне пришлось сделать ссылку на сборку. Если вы используете VB.NET, вам не нужно ничего делать. Класс, который мы собираемся использовать, - это WindowsFormsApplicationBase, и унаследовать от него наш диспетчер экземпляров, а затем использовать свойства и события для обработки единственного экземпляра.

public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
    private SingleInstanceApplication _application;
    private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;

    public SingleInstanceManager()
    {
        IsSingleInstance = true;
    }

    protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
    {
        // First time _application is launched
        _commandLine = eventArgs.CommandLine;
        _application = new SingleInstanceApplication();
        _application.Run();
        return false;
    }

    protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
    {
        // Subsequent launches
        base.OnStartupNextInstance(eventArgs);
        _commandLine = eventArgs.CommandLine;
        _application.Activate();
    }
}

По сути, мы используем биты VB для обнаружения отдельных экземпляров и соответствующей обработки. OnStartup будет запущен при загрузке первого экземпляра. OnStartupNextInstance запускается при повторном запуске приложения. Как видите, я могу добраться до того, что было передано в командной строке, с помощью аргументов события. Я установил значение в поле экземпляра. Вы можете проанализировать командную строку здесь или передать ее своему приложению через конструктор и вызов метода Activate.

В-третьих, пора создать нашу EntryPoint. Вместо того, чтобы обновлять приложение, как обычно, мы воспользуемся нашим SingleInstanceManager.

public class EntryPoint
{
    [STAThread]
    public static void Main(string[] args)
    {
        SingleInstanceManager manager = new SingleInstanceManager();
        manager.Run(args);
    }
}

Что ж, я надеюсь, что вы сможете все уследить, сможете использовать эту реализацию и сделать ее своей собственной.

person Dale Ragan    schedule 21.08.2008
comment
Мы делаем это именно так, и я никогда не был слишком доволен этим из-за зависимости от WinForms. - person Bob King; 24.09.2008
comment
Я бы остановился на решении мьютекса, потому что оно не имеет ничего общего с формами. - person Steven Sudit; 13.07.2009
comment
Я использовал это, потому что у меня были проблемы с другими подходами, но я почти уверен, что он использует удаленное взаимодействие под капотом. У моего приложения были две связанные проблемы: некоторые клиенты говорят, что оно пытается дозвониться домой, даже если они сказали ему не делать этого. При более внимательном рассмотрении видно, что соединение идет на localhost. Тем не менее, они изначально этого не знают. Кроме того, я не могу использовать удаленное взаимодействие для другой цели (я так думаю?), Потому что он уже используется для этого. Когда я попробовал использовать мьютекс, я снова смог использовать удаленное взаимодействие. - person Richard Watson; 13.01.2011
comment
Простите меня, но если я чего-то не упустил, вы избежали написания трех строк кода и вместо этого повторно использовали фреймворк, просто чтобы написать довольно тяжелый код для этого. Так где же экономия? - person greenoldman; 13.08.2011
comment
это можно сделать в winforms? - person Jack; 02.06.2012
comment
Если вы не вызовете InitializeComponent () в экземпляре приложения, вы не сможете разрешить ресурсы ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run (); - person Nick; 25.07.2013
comment
Как использовать это в WinForms !? - person ACE; 17.03.2015
comment
Я не голосую за него из-за его зависимости от Winforms. - person Saraf Talukder; 02.06.2015
comment
Это не работает с пользовательским uri. Он просто открывает другое приложение и ничего не делает. - person DirectionUnkown; 06.06.2019
comment
Этот подход описан в официальных примерах Microsoft WPF: github.com/ microsoft / WPF-Samples / tree / master / Я не понимаю ненависти к зависимостям WinForms, поскольку, как упомянул автор, речь идет о повторном использовании компонентов фреймворка, а не о том, чтобы изобретать колесо. Я лично не думаю, что самостоятельно настраиваемое решение мьютекса легче поддерживать, чем готовые компоненты в рамках. WinForm поставляется вместе с фреймворком, а не с дополнительной зависимостью. Что касается производительности, то нужны меры. - person Lee Song; 17.07.2019

Из здесь.

Обычно межпроцессный Mutex используется для обеспечения одновременного выполнения только экземпляра программы. Вот как это делается:

class OneAtATimePlease {

  // Use a name unique to the application (eg include your company URL)
  static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");

  static void Main()
  {
    // Wait 5 seconds if contended – in case another instance
    // of the program is in the process of shutting down.
    if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
    {
        Console.WriteLine("Another instance of the app is running. Bye!");
        return;
    }

    try
    {    
        Console.WriteLine("Running - press Enter to exit");
        Console.ReadLine();
    }
    finally
    {
        mutex.ReleaseMutex();
    }    
  }    
}

Хорошая особенность Mutex заключается в том, что если приложение завершается без предварительного вызова ReleaseMutex, среда CLR автоматически освобождает Mutex.

person jason saldo    schedule 21.08.2008
comment
Должен сказать, мне нравится этот ответ намного больше, чем принятый, просто потому, что он не зависит от WinForms. Лично большая часть моей разработки была перенесена на WPF, и я не хочу тянуть библиотеки WinForm для чего-то вроде этого. - person Switters; 27.10.2008
comment
Конечно, чтобы быть полным ответом, вы также должны описать передачу аргументов другому экземпляру :) - person Simon Buchan; 28.11.2008
comment
@ Джейсон, хорошо, спасибо! Но я предпочитаю не пропускать тайм-аут. Это настолько субъективно и зависит от многих переменных. Если вы когда-нибудь захотите запустить другое приложение, просто отпустите мьютекс быстрее .. например, как только пользователь подтвердит закрытие - person Eric Ouellet; 06.12.2013
comment
@EricOuellet: Это делают почти все программы, в которых есть вкладки - Photoshop, Sublime Text, Chrome .... Если у вас есть веская причина для создания мастер-процесса (скажем, у вас есть внутренняя база данных для настроек), вы можете захотеть пусть он покажет пользовательский интерфейс, как если бы это был новый процесс. - person Simon Buchan; 06.12.2013
comment
@ Саймон, ты прав. Я просто задаюсь вопросом об очень старой вещи ... MDI vs SDI (Многодокументный интерфейс vs Однодокументный интерфейс). Когда вы говорите о вкладках, вы имеете в виду MDI. В 1998 году в одной из книг Microsoft предлагается удалить все приложения MDI. Microsoft переключила Word, Excel ... на SDI, что, на мой взгляд, проще и лучше. Я понимаю, что Chrome и другие (теперь IE) хотят вернуться к MDI. Я лично (исходя из ничего / личных ощущений), что все же лучше открыть новое приложение, когда выбран файл assoc. Но теперь я лучше понимаю заданный вопрос. Спасибо ! - person Eric Ouellet; 06.12.2013
comment
@EricOuellet: Проблемы с MDI, вероятно, были больше связаны с наличием окон внутри окон, чем с запутанными вкладками, и есть сильный толчок к тому, чтобы все вкладки вытягивались в свое собственное окно верхнего уровня, что смягчало это. Однако это все проблемы пользовательского интерфейса, которые не должны иметь никакого отношения к тому, как вы разделяете свои процессы (несколько процессов могут содержаться в одном окне, и наоборот). - person Simon Buchan; 07.12.2013

На самом деле в MSDN есть образец приложения для C # и VB, чтобы делать именно это: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Наиболее распространенный и надежный метод разработки обнаружения отдельных экземпляров - использование инфраструктуры удаленного взаимодействия Microsoft .NET Framework (System.Remoting). Microsoft .NET Framework (версия 2.0) включает тип WindowsFormsApplicationBase, который инкапсулирует необходимые функции удаленного взаимодействия. Чтобы включить этот тип в приложение WPF, тип должен быть производным от него и использоваться в качестве прокладки между методом статической точки входа приложения Main и типом Application приложения WPF. Оболочка определяет, когда приложение запускается впервые и когда предпринимаются последующие запуски, и дает элемент управления типом приложения WPF, чтобы определить, как обрабатывать запуски.

  • Для C # люди просто делают глубокий вдох и забывают о том, что «я не хочу включать VisualBasic DLL». Из-за этого и того, что Скотт Хансельман говорит и тот факт, что это в значительной степени самое чистое решение проблемы и разработано людьми, которые много знают больше о фреймворке, чем вы.
  • С точки зрения удобства использования дело в том, что если ваш пользователь загружает приложение, и оно уже открыто, и вы даете ему сообщение об ошибке, например 'Another instance of the app is running. Bye', то он не будет очень счастливым пользователем. Вы просто ДОЛЖНЫ (в приложении с графическим интерфейсом) переключиться на это приложение и передать предоставленные аргументы - или, если параметры командной строки не имеют значения, вы должны открыть приложение, которое могло быть свернуто.

Фреймворк уже поддерживает это - просто какой-то идиот назвал DLL Microsoft.VisualBasic, и она не попала в Microsoft.ApplicationUtils или что-то в этом роде. Преодолейте это - или откройте Reflector.

Совет: если вы используете этот подход точно так же, как есть, и у вас уже есть App.xaml с ресурсами и т. Д., Вам нужно взгляните и на это тоже.

person Simon_Weaver    schedule 27.09.2010
comment
Благодарим вас за ссылку "взгляните на это тоже". Это именно то, что мне было нужно. Кстати, решение №3 в вашей ссылке - лучшее. - person Eternal21; 16.11.2012
comment
Я также сторонник делегирования полномочий фреймворку и специально разработанным библиотекам, где это возможно. - person Eniola; 14.07.2016
comment
Я поддерживаю сварливость. - person Mike Nakis; 01.03.2021

Этот код должен перейти к основному методу. Подробнее см. здесь информация об основном методе в WPF.

[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);

private const int SW_SHOWMAXIMIZED = 3;

static void Main() 
{
    Process currentProcess = Process.GetCurrentProcess();
    var runningProcess = (from process in Process.GetProcesses()
                          where
                            process.Id != currentProcess.Id &&
                            process.ProcessName.Equals(
                              currentProcess.ProcessName,
                              StringComparison.Ordinal)
                          select process).FirstOrDefault();
    if (runningProcess != null)
    {
        ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
       return; 
    }
}

Способ 2

static void Main()
{
    string procName = Process.GetCurrentProcess().ProcessName;
    // get the list of all processes by that name

    Process[] processes=Process.GetProcessesByName(procName);

    if (processes.Length > 1)
    {
        MessageBox.Show(procName + " already running");  
        return;
    } 
    else
    {
        // Application.Run(...);
    }
}

Примечание. Приведенные выше методы предполагают, что ваш процесс / приложение имеет уникальное имя. Потому что он использует имя процесса, чтобы найти существующие процессоры. Итак, если ваше приложение имеет очень распространенное имя (например, «Блокнот»), описанный выше подход не будет работать.

person CharithJ    schedule 24.08.2011
comment
Кроме того, это не сработает, если на вашем компьютере работает какая-либо другая программа с таким же именем. ProcessName возвращает имя исполняемого файла за вычетом exe. Если вы создадите приложение под названием «Блокнот», и блокнот Windows запущен, он определит его как запущенное приложение. - person Jcl; 26.01.2015
comment
Спасибо за этот ответ. Я нашел так много похожих вопросов, и ответы всегда были настолько сложными и / или запутанными, что я считал их бесполезными. Этот (метод №1) прост, понятен и, прежде всего, действительно помог мне запустить мой код. - person ElDoRado1239; 30.05.2017

Что ж, у меня для этого есть одноразовый класс, который легко работает для большинства случаев использования:

Используйте это так:

static void Main()
{
    using (SingleInstanceMutex sim = new SingleInstanceMutex())
    {
        if (sim.IsOtherInstanceRunning)
        {
            Application.Exit();
        }

        // Initialize program here.
    }
}

Вот:

/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
    #region Fields

    /// <summary>
    /// Indicator whether another instance of this application is running or not.
    /// </summary>
    private bool isNoOtherInstanceRunning;

    /// <summary>
    /// The <see cref="Mutex"/> used to ask for other instances of this application.
    /// </summary>
    private Mutex singleInstanceMutex = null;

    /// <summary>
    /// An indicator whether this object is beeing actively disposed or not.
    /// </summary>
    private bool disposed;

    #endregion

    #region Constructor

    /// <summary>
    /// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
    /// </summary>
    public SingleInstanceMutex()
    {
        this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an indicator whether another instance of the application is running or not.
    /// </summary>
    public bool IsOtherInstanceRunning
    {
        get
        {
            return !this.isNoOtherInstanceRunning;
        }
    }

    #endregion

    #region Methods

    /// <summary>
    /// Closes the <see cref="SingleInstanceMutex"/>.
    /// </summary>
    public void Close()
    {
        this.ThrowIfDisposed();
        this.singleInstanceMutex.Close();
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if (!this.disposed)
        {
            /* Release unmanaged ressources */

            if (disposing)
            {
                /* Release managed ressources */
                this.Close();
            }

            this.disposed = true;
        }
    }

    /// <summary>
    /// Throws an exception if something is tried to be done with an already disposed object.
    /// </summary>
    /// <remarks>
    /// All public methods of the class must first call this.
    /// </remarks>
    public void ThrowIfDisposed()
    {
        if (this.disposed)
        {
            throw new ObjectDisposedException(this.GetType().Name);
        }
    }

    #endregion
}
person Oliver Friedrich    schedule 19.02.2010
comment
это было довольно легко заставить работать. Второе приложение не закроется, пока я не изменю Application.Exit (); к простому возврату; но в остальном это здорово. Хотя я признаю, что собираюсь взглянуть на предыдущее решение поближе, поскольку оно использует интерфейс. blogs.microsoft. co.il/blogs/arik/archive/2010/05/28/ - person hal9000; 12.08.2011

Новый, который использует Mutex и IPC, а также передает любые аргументы командной строки работающему экземпляру, - это Одноэкземплярное приложение WPF.

person huseyint    schedule 28.05.2010
comment
Пользуюсь этим с большим успехом. Если вы включите NamedPipes с этим, вы также можете передать аргументы командной строки исходному приложению. Класс SingleInstance.cs был написан Microsoft. Я добавил еще одну ссылку на более читаемую версию блога Арика Познанского на CodeProject. - person Riegardt Steyn; 26.02.2014
comment
Ссылка сейчас не работает. - person Mike Lowery; 09.06.2020
comment
Попробуйте это (та же дата, имя того же автора, предположительно та же статья): codeproject.com/articles/84270/wpf-single-instance-application - person Daerst; 24.06.2020

Код C # .NET Single Instance Application, который является эталоном для отмеченного ответа, является отличным Начните.

Однако я обнаружил, что он не очень хорошо справляется со случаями, когда в уже существующем экземпляре открыто модальное диалоговое окно, независимо от того, является ли этот диалог управляемым (например, другая форма, например, поле о) или неуправляемым (например, OpenFileDialog даже при использовании стандартного класса .NET). В исходном коде основная форма активируется, но модальная форма остается неактивной, что выглядит странно, к тому же пользователь должен щелкнуть по ней, чтобы продолжить использование приложения.

Итак, я создал служебный класс SingleInstance для автоматической обработки всего этого для приложений Winforms и WPF.

Winforms:

1) измените класс Program следующим образом:

static class Program
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);

    [STAThread]
    static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

2) измените класс главного окна следующим образом:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    protected override void WndProc(ref Message m)
    {
        // if needed, the singleton will restore this window
        Program.Singleton.OnWndProc(this, m, true);

        // TODO: handle specific messages here if needed
        base.WndProc(ref m);
    }
}

WPF:

1) измените страницу приложения следующим образом (и убедитесь, что вы установили его действие сборки на page, чтобы иметь возможность переопределить метод Main):

public partial class App : Application
{
    public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);

    [STAThread]
    public static void Main(string[] args)
    {
        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        Singleton.RunFirstInstance(() =>
        {
            SingleInstanceMain(args);
        });
    }

    public static void SingleInstanceMain(string[] args)
    {
        // standard code that was in Main now goes here
        App app = new App();
        app.InitializeComponent();
        app.Run();
    }
}

2) измените класс главного окна следующим образом:

public partial class MainWindow : Window
{
    private HwndSource _source;

    public MainWindow()
    {
        InitializeComponent();
    }

    protected override void OnSourceInitialized(EventArgs e)
    {
        base.OnSourceInitialized(e);
        _source = (HwndSource)PresentationSource.FromVisual(this);
        _source.AddHook(HwndSourceHook);
    }

    protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
    {
        // if needed, the singleton will restore this window
        App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);

        // TODO: handle other specific message
        return IntPtr.Zero;
    }

А вот и служебный класс:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;

namespace SingleInstanceUtilities
{
    public sealed class SingleInstance
    {
        private const int HWND_BROADCAST = 0xFFFF;

        [DllImport("user32.dll")]
        private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);

        [DllImport("user32.dll", CharSet = CharSet.Unicode)]
        private static extern int RegisterWindowMessage(string message);

        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);

        public SingleInstance(string uniqueName)
        {
            if (uniqueName == null)
                throw new ArgumentNullException("uniqueName");

            Mutex = new Mutex(true, uniqueName);
            Message = RegisterWindowMessage("WM_" + uniqueName);
        }

        public Mutex Mutex { get; private set; }
        public int Message { get; private set; }

        public void RunFirstInstance(Action action)
        {
            RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
        }

        // NOTE: if this always return false, close & restart Visual Studio
        // this is probably due to the vshost.exe thing
        public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
        {
            if (action == null)
                throw new ArgumentNullException("action");

            if (WaitForMutext(wParam, lParam))
            {
                try
                {
                    action();
                }
                finally
                {
                    ReleaseMutex();
                }
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            if (hwnd == IntPtr.Zero)
                return;

            FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
        }

        public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
        {
            if (m == Message)
            {
                if (restorePlacement)
                {
                    WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
                    if (placement.IsValid && placement.IsMinimized)
                    {
                        const int SW_SHOWNORMAL = 1;
                        placement.ShowCmd = SW_SHOWNORMAL;
                        placement.SetPlacement(hwnd);
                    }
                }

                if (activate)
                {
                    SetForegroundWindow(hwnd);
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
                }
            }
        }

#if WINFORMS // define this for Winforms apps
        public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            if (m == Message)
            {
                if (activate)
                {
                    if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
                    {
                        form.WindowState = System.Windows.Forms.FormWindowState.Normal;
                    }

                    form.Activate();
                    FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
                }
            }
        }

        public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
        {
            if (form == null)
                throw new ArgumentNullException("form");

            OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
        }
#endif

        public void ReleaseMutex()
        {
            Mutex.ReleaseMutex();
        }

        public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
        {
            bool b = PrivateWaitForMutext(force);
            if (!b)
            {
                PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
            }
            return b;
        }

        public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
        {
            return WaitForMutext(false, wParam, lParam);
        }

        private bool PrivateWaitForMutext(bool force)
        {
            if (force)
                return true;

            try
            {
                return Mutex.WaitOne(TimeSpan.Zero, true);
            }
            catch (AbandonedMutexException)
            {
                return true;
            }
        }
    }

    // NOTE: don't add any field or public get/set property, as this must exactly map to Windows' WINDOWPLACEMENT structure
    [StructLayout(LayoutKind.Sequential)]
    public struct WindowPlacement
    {
        public int Length { get; set; }
        public int Flags { get; set; }
        public int ShowCmd { get; set; }
        public int MinPositionX { get; set; }
        public int MinPositionY { get; set; }
        public int MaxPositionX { get; set; }
        public int MaxPositionY { get; set; }
        public int NormalPositionLeft { get; set; }
        public int NormalPositionTop { get; set; }
        public int NormalPositionRight { get; set; }
        public int NormalPositionBottom { get; set; }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);

        private const int SW_SHOWMINIMIZED = 2;

        public bool IsMinimized
        {
            get
            {
                return ShowCmd == SW_SHOWMINIMIZED;
            }
        }

        public bool IsValid
        {
            get
            {
                return Length == Marshal.SizeOf(typeof(WindowPlacement));
            }
        }

        public void SetPlacement(IntPtr windowHandle)
        {
            SetWindowPlacement(windowHandle, ref this);
        }

        public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
        {
            WindowPlacement placement = new WindowPlacement();
            if (windowHandle == IntPtr.Zero)
                return placement;

            placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
            if (!GetWindowPlacement(windowHandle, ref placement))
            {
                if (throwOnError)
                    throw new Win32Exception(Marshal.GetLastWin32Error());

                return new WindowPlacement();
            }
            return placement;
        }
    }

    public static class FormUtilities
    {
        [DllImport("user32.dll")]
        private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetActiveWindow(IntPtr hWnd);

        [DllImport("user32.dll")]
        private static extern bool IsWindowVisible(IntPtr hWnd);

        [DllImport("kernel32.dll")]
        public static extern int GetCurrentThreadId();

        private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);

        [DllImport("user32.dll")]
        private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);

        private class ModalWindowUtil
        {
            private const int GW_OWNER = 4;
            private int _maxOwnershipLevel;
            private IntPtr _maxOwnershipHandle;

            private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
            {
                int level = 1;
                if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
                {
                    if (level > _maxOwnershipLevel)
                    {
                        _maxOwnershipHandle = hwnd;
                        _maxOwnershipLevel = level;
                    }
                }
                return true;
            }

            private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
            {
                IntPtr o = GetWindow(hwnd, GW_OWNER);
                if (o == IntPtr.Zero)
                    return false;

                if (o == owner)
                    return true;

                level++;
                return IsOwned(owner, o, ref level);
            }

            public static void ActivateWindow(IntPtr hwnd)
            {
                if (hwnd != IntPtr.Zero)
                {
                    SetActiveWindow(hwnd);
                }
            }

            public static IntPtr GetModalWindow(IntPtr owner)
            {
                ModalWindowUtil util = new ModalWindowUtil();
                EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
                return util._maxOwnershipHandle; // may be IntPtr.Zero
            }
        }

        public static void ActivateWindow(IntPtr hwnd)
        {
            ModalWindowUtil.ActivateWindow(hwnd);
        }

        public static IntPtr GetModalWindow(IntPtr owner)
        {
            return ModalWindowUtil.GetModalWindow(owner);
        }
    }
}
person Simon Mourier    schedule 13.05.2013

Просто несколько мыслей: бывают случаи, когда требуется, чтобы только один экземпляр приложения не был «хромым», как некоторые думают. Приложения баз данных и т. Д. На порядок сложнее, если разрешить нескольким экземплярам приложения для одного пользователя получить доступ к базе данных (вы знаете, все это обновление всех записей, открытых в нескольких экземплярах приложения для пользователей машина и т. д.). Во-первых, для «конфликта имен» не используйте имя, удобочитаемое человеком - используйте вместо него GUID или, что еще лучше, GUID + удобочитаемое имя. Вероятность столкновения имен просто упала из поля зрения, и Mutex все равно Как кто-то заметил, DOS-атака будет отстой, но если злоумышленник позаботился о том, чтобы получить имя мьютекса и встроить его в свое приложение, вы все равно в значительной степени являетесь целью, и вам придется сделать ГЛАВНОЕ больше для защиты. самостоятельно, чем просто возиться с именем мьютекса. Кроме того, если вы используете вариант: new Mutex (true, «некоторый GUID плюс имя», вне AIsFirstInstance), у вас уже есть индикатор того, является ли Mutex первым экземпляром.

person Bruce    schedule 17.12.2009

Вот пример, который позволяет вам иметь единственный экземпляр приложения. Когда загружаются новые экземпляры, они передают свои аргументы основному запущенному экземпляру.

public partial class App : Application
{
    private static Mutex SingleMutex;
    public static uint MessageId;

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        IntPtr Result;
        IntPtr SendOk;
        Win32.COPYDATASTRUCT CopyData;
        string[] Args;
        IntPtr CopyDataMem;
        bool AllowMultipleInstances = false;

        Args = Environment.GetCommandLineArgs();

        // TODO: Replace {00000000-0000-0000-0000-000000000000} with your application's GUID
        MessageId   = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
        SingleMutex = new Mutex(false, "AppName");

        if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
        {
            new Main();
        }
        else if (Args.Length > 1)
        {
            foreach (Process Proc in Process.GetProcesses())
            {
                SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    2000, out Result);

                if (SendOk == IntPtr.Zero)
                    continue;
                if ((uint)Result != MessageId)
                    continue;

                CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));

                CopyData.dwData = IntPtr.Zero;
                CopyData.cbData = Args[1].Length*2;
                CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);

                Marshal.StructureToPtr(CopyData, CopyDataMem, false);

                Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
                    Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
                    5000, out Result);

                Marshal.FreeHGlobal(CopyData.lpData);
                Marshal.FreeHGlobal(CopyDataMem);
            }

            Shutdown(0);
        }
    }
}

public partial class Main : Window
{
    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        HwndSource Source;

        Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
        Source.AddHook(new HwndSourceHook(Window_Proc));
    }

    private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
    {
        Win32.COPYDATASTRUCT CopyData;
        string Path;

        if (Msg == Win32.WM_COPYDATA)
        {
            CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
            Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);

            if (WindowState == WindowState.Minimized)
            {
                // Restore window from tray
            }

            // Do whatever we want with information

            Activate();
            Focus();
        }

        if (Msg == App.MessageId)
        {
            Handled = true;
            return new IntPtr(App.MessageId);
        }

        return IntPtr.Zero;
    }
}

public class Win32
{
    public const uint WM_COPYDATA = 0x004A;

    public struct COPYDATASTRUCT
    {
        public IntPtr dwData;
        public int    cbData;
        public IntPtr lpData;
    }

    [Flags]
    public enum SendMessageTimeoutFlags : uint
    {
        SMTO_NORMAL             = 0x0000,
        SMTO_BLOCK              = 0x0001,
        SMTO_ABORTIFHUNG        = 0x0002,
        SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
    }

    [DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
    public static extern uint RegisterWindowMessage(string lpString);
    [DllImport("user32.dll")]
    public static extern IntPtr SendMessageTimeout(
        IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
        SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}
person Nathan Moinvaziri    schedule 07.05.2011
comment
Это действительно хороший пример того, что мне делать. Натан, все ли аргументы отправляются с использованием этого метода? У меня в приложении 7 или около того, и я думаю, что этот код будет работать. - person kevp; 30.05.2012
comment
В моем примере отправляется только первый аргумент, но его можно изменить так, чтобы отправлялись все аргументы. - person Nathan Moinvaziri; 04.06.2012

Так много ответов на такой, казалось бы, простой вопрос. Чтобы немного встряхнуть, вот мое решение этой проблемы.

Создание Mutex может быть проблематичным, потому что JIT-исполнитель видит, что вы используете его только для небольшой части вашего кода, и хочет пометить его как готовый для сборки мусора. Он в значительной степени хочет перехитрить вас, думая, что вы не собираетесь использовать этот Mutex так долго. На самом деле вы хотите, чтобы этот мьютекс зависел до тех пор, пока работает ваше приложение. Лучший способ сказать сборщику мусора, чтобы он оставил вас Mutex в покое, - это сказать ему, чтобы он оставался живым, несмотря на разные поколения гаражной сборки. Пример:

var m = new Mutex(...);
...
GC.KeepAlive(m);

Я поднял идею с этой страницы: http://www.ai.uga.edu/~mc/SingleInstance.html

person Peter    schedule 05.05.2010
comment
Не было бы проще сохранить общую копию в классе приложения? - person rossisdead; 26.05.2010

Похоже, есть действительно хороший способ справиться с этим:

WPF Single Экземпляр приложения

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

person Joel Barsotti    schedule 11.08.2011
comment
Когда я попробовал, это не выдвинуло существующее окно на передний план. - person RandomEngy; 29.10.2012

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

Он ориентирован на WPF, поскольку использует класс System.Windows.StartupEventHandler, но его можно легко изменить.

Этот код требует ссылки на PresentationFramework и System.ServiceModel.

Использование:

class Program
{
    static void Main()
    {
        var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");

        if (SingleInstanceManager.VerifySingleInstance(applicationId))
        {
            SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;

            // Start the application
        }
    }

    static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
    {
        // Do something in response to another instance starting up.
    }
}

Исходный код:

/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
  /// <summary>
  /// Raised when another instance attempts to start up.
  /// </summary>
  public static event StartupEventHandler OtherInstanceStarted;

  /// <summary>
  /// Checks to see if this instance is the first instance running on this machine.  If it is not, this method will
  /// send the main instance this instance's startup information.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if this instance is the main instance.</returns>
  public static bool VerifySingleInstace(Guid guid)
  {
    if (!AttemptPublishService(guid))
    {
      NotifyMainInstance(guid);

      return false;
    }

    return true;
  }

  /// <summary>
  /// Attempts to publish the service.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  /// <returns>True if the service was published successfully.</returns>
  private static bool AttemptPublishService(Guid guid)
  {
    try
    {
      ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
      NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
      serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
      serviceHost.Open();

      return true;
    }
    catch
    {
      return false;
    }
  }

  /// <summary>
  /// Notifies the main instance that this instance is attempting to start up.
  /// </summary>
  /// <param name="guid">The application's unique identifier.</param>
  private static void NotifyMainInstance(Guid guid)
  {
    NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
    EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
    using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
    {
      ISingleInstance singleInstance = factory.CreateChannel();
      singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
    }
  }

  /// <summary>
  /// Creates an address to publish/contact the service at based on a globally unique identifier.
  /// </summary>
  /// <param name="guid">The identifier for the application.</param>
  /// <returns>The address to publish/contact the service.</returns>
  private static string CreateAddress(Guid guid)
  {
    return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
  }

  /// <summary>
  /// The interface that describes the single instance service.
  /// </summary>
  [ServiceContract]
  private interface ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    [OperationContract]
    void NotifyMainInstance(string[] args);
  }

  /// <summary>
  /// The implementation of the single instance service interface.
  /// </summary>
  private class SingleInstance : ISingleInstance
  {
    /// <summary>
    /// Notifies the main instance that another instance of the application attempted to start.
    /// </summary>
    /// <param name="args">The other instance's command-line arguments.</param>
    public void NotifyMainInstance(string[] args)
    {
      if (OtherInstanceStarted != null)
      {
        Type type = typeof(StartupEventArgs);
        ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
        StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
        FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
        Debug.Assert(argsField != null);
        argsField.SetValue(e, args);

        OtherInstanceStarted(null, e);
      }
    }
  }
}
person Dan    schedule 19.04.2013

Посмотрите на следующий код. Это отличное и простое решение для предотвращения нескольких экземпляров приложения WPF.

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process thisProc = Process.GetCurrentProcess();
    if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
    {
        MessageBox.Show("Application running");
        Application.Current.Shutdown();
        return;
    }

    var wLogin = new LoginWindow();

    if (wLogin.ShowDialog() == true)
    {
        var wMain = new Main();
        wMain.WindowState = WindowState.Maximized;
        wMain.Show();
    }
    else
    {
        Application.Current.Shutdown();
    }
}
person carlito    schedule 30.11.2012

Вот что я использую. Он объединил перечисление процессов для выполнения переключения и мьютекс для защиты от «активных кликеров»:

public partial class App
{
    [DllImport("user32")]
    private static extern int OpenIcon(IntPtr hWnd);

    [DllImport("user32.dll")]
    private static extern bool SetForegroundWindow(IntPtr hWnd);

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);
        var p = Process
           .GetProcessesByName(Process.GetCurrentProcess().ProcessName);
            foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
            {
                OpenIcon(t.MainWindowHandle);
                SetForegroundWindow(t.MainWindowHandle);
                Current.Shutdown();
                return;
            }

            // there is a chance the user tries to click on the icon repeatedly
            // and the process cannot be discovered yet
            bool createdNew;
            var mutex = new Mutex(true, "MyAwesomeApp", 
               out createdNew);  // must be a variable, though it is unused - 
            // we just need a bit of time until the process shows up
            if (!createdNew)
            {
                Current.Shutdown();
                return;
            }

            new Bootstrapper().Run();
        }
    }
person Sergey Aldoukhov    schedule 22.06.2010
comment
Что здесь за Bootstrapper? - person WitVault; 14.04.2021

Я нашел более простое решение, похожее на решение Дейла Рагана, но немного измененное. Он делает практически все, что вам нужно, и основан на стандартном классе Microsoft WindowsFormsApplicationBase.

Во-первых, вы создаете класс SingleInstanceController, который можно использовать во всех других приложениях с одним экземпляром, которые используют Windows Forms:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;


namespace SingleInstanceController_NET
{
    public class SingleInstanceController
    : WindowsFormsApplicationBase
    {
        public delegate Form CreateMainForm();
        public delegate void StartNextInstanceDelegate(Form mainWindow);
        CreateMainForm formCreation;
        StartNextInstanceDelegate onStartNextInstance;
        public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
        {
            // Set whether the application is single instance
            this.formCreation = formCreation;
            this.onStartNextInstance = onStartNextInstance;
            this.IsSingleInstance = true;

            this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);                      
        }

        void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
        {
            if (onStartNextInstance != null)
            {
                onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
                                                    // for example, by clicking on the exe file.
            }                                       // This code can determine how to re-activate the existing main window of the running application.
        }

        protected override void OnCreateMainForm()
        {
            // Instantiate your main application form
            this.MainForm = formCreation();
        }

        public void Run()
        {
            string[] commandLine = new string[0];
            base.Run(commandLine);
        }
    }
}

Затем вы можете использовать его в своей программе следующим образом:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;

namespace SingleInstance
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        static Form CreateForm()
        {
            return new Form1(); // Form1 is used for the main window.
        }

        static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
                                                         // the main window is activated again.
        {
            mainWindow.WindowState = FormWindowState.Maximized;
        }
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);            
            SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
            controller.Run();         
        }
    }
}

И программа, и решение SingleInstanceController_NET ​​должны ссылаться на Microsoft.VisualBasic. Если вы просто хотите повторно активировать запущенное приложение как обычное окно, когда пользователь пытается перезапустить запущенную программу, второй параметр в SingleInstanceController может иметь значение null. В данном примере окно развернуто.

person Mikhail Semenov    schedule 04.04.2011

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

person Matt Davison    schedule 26.01.2009
comment
Никогда не используйте именованный мьютекс - никогда не говорите никогда. Если на моей машине запущен вредоносный код, вероятно, я уже заболел. - person Joe; 18.07.2009
comment
На самом деле это даже не обязательно должен быть вредоносный код. Это могло быть просто случайное совпадение имен. - person Matt Davison; 21.07.2009
comment
Лучше спросить, по какой возможной причине вы хотели бы такого поведения. Не создавайте свое приложение как приложение с одним экземпляром =). Я знаю, что это неубедительный ответ, но с точки зрения дизайна это почти всегда правильный ответ. Не зная больше о приложении, трудно сказать больше. - person Matt Davison; 24.10.2009
comment
По крайней мере, в Windows у мьютексов есть контроль доступа, так что с вашим объектом можно поиграть. Что касается самих коллизий, поэтому и придуманы UUID / GUID. - person NuSkooler; 12.10.2010

Обновление 2017-01-25. Попробовав несколько вещей, я решил пойти с VisualBasic.dll, это проще и работает лучше (по крайней мере, для меня). Я позволю своему предыдущему ответу просто справиться ...

В качестве справки, вот как я поступил без передачи аргументов (я не могу найти для этого никаких оснований ... я имею в виду одно приложение с аргументами, которые должны передаваться из одного экземпляра в другой). Если требуется ассоциация файлов, то приложение должно (в соответствии со стандартными ожиданиями пользователей) создаваться для каждого документа. Если вам нужно передать аргументы в существующее приложение, я бы использовал vb dll.

Не передавая аргументы (только приложение с одним экземпляром), я предпочитаю не регистрировать новое сообщение Window и не переопределять цикл сообщений, как определено в Решении Мэтта Дэвиса. Хотя добавить VisualBasic dll несложно, но я предпочитаю не добавлять новую ссылку только для того, чтобы создать приложение с одним экземпляром. Кроме того, я предпочитаю создавать экземпляр нового класса с помощью Main вместо вызова Shutdown из переопределения App.Startup, чтобы обеспечить выход как можно скорее.

Надеюсь, что кому-то понравится ... или немного вдохновит :-)

Класс запуска проекта должен быть установлен как «SingleInstanceApp».

public class SingleInstanceApp
{
    [STAThread]
    public static void Main(string[] args)
    {
        Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");

        if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
        {
            try
            {
                var app = new App();
                app.InitializeComponent();
                app.Run();

            }
            finally
            {
                _mutexSingleInstance.ReleaseMutex();
                _mutexSingleInstance.Close();
            }
        }
        else
        {
            MessageBox.Show("One instance is already running.");

            var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
            {
                if (processes.Length > 1)
                {
                    foreach (var process in processes)
                    {
                        if (process.Id != Process.GetCurrentProcess().Id)
                        {
                            WindowHelper.SetForegroundWindow(process.MainWindowHandle);
                        }
                    }
                }
            }
        }
    }
}

WindowHelper:

using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;

namespace HQ.Util.Unmanaged
{
    public class WindowHelper
    {
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool SetForegroundWindow(IntPtr hWnd);
person Eric Ouellet    schedule 05.12.2013

Но не используя Mutex, простой ответ:

System.Diagnostics;    
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;

if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

Поместите его в Program.Main().
Пример:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;

namespace Sample
{
    static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            //simple add Diagnostics namespace, and these 3 lines below 
            string thisprocessname = Process.GetCurrentProcess().ProcessName;
            if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
                return;

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Sample());
        }
    }
}

Вы можете добавить MessageBox.Show к if-выражению и поставить «Приложение уже запущено».
Это может быть кому-то полезно.

person newbieguy    schedule 30.12.2016
comment
Если два процесса запускаются одновременно, они оба могут видеть два активных процесса и автоматически завершаться. - person A.T.; 19.08.2017
comment
@В. Да, верно, это также может быть полезно для приложений, работающих от имени администратора или еще - person newbieguy; 22.08.2017
comment
Если вы сделаете копию своего приложения и переименуете ее, вы сможете запустить оригинал и копию одновременно. - person Dominique Bijnens; 25.01.2020

Я добавил метод sendMessage в класс NativeMethods.

Очевидно, метод postmessage работает, если приложение не отображается на панели задач, однако использование метода sendmessage решает эту проблему.

class NativeMethods
{
    public const int HWND_BROADCAST = 0xffff;
    public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
    [DllImport("user32")]
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
    [DllImport("user32")]
    public static extern int RegisterWindowMessage(string message);
}
person Martin Bech    schedule 26.01.2015

Подходы на основе именованных мьютексов не являются кроссплатформенными, поскольку именованные мьютексы не являются глобальными в Mono. Подходы, основанные на перечислении процессов, не имеют никакой синхронизации и могут привести к некорректному поведению (например, несколько процессов, запущенных одновременно, могут все само завершаться в зависимости от времени). Подходы на основе оконных систем нежелательны в консольном приложении. Это решение, основанное на ответе Divin, решает все эти проблемы:

using System;
using System.IO;

namespace TestCs
{
    public class Program
    {
        // The app id must be unique. Generate a new guid for your application. 
        public static string AppId = "01234567-89ab-cdef-0123-456789abcdef";

        // The stream is stored globally to ensure that it won't be disposed before the application terminates.
        public static FileStream UniqueInstanceStream;

        public static int Main(string[] args)
        {
            EnsureUniqueInstance();

            // Your code here.

            return 0;
        }

        private static void EnsureUniqueInstance()
        {
            // Note: If you want the check to be per-user, use Environment.SpecialFolder.ApplicationData instead.
            string lockDir = Path.Combine(
                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                "UniqueInstanceApps");
            string lockPath = Path.Combine(lockDir, $"{AppId}.unique");

            Directory.CreateDirectory(lockDir);

            try
            {
                // Create the file with exclusive write access. If this fails, then another process is executing.
                UniqueInstanceStream = File.Open(lockPath, FileMode.Create, FileAccess.Write, FileShare.None);

                // Although only the line above should be sufficient, when debugging with a vshost on Visual Studio
                // (that acts as a proxy), the IO exception isn't passed to the application before a Write is executed.
                UniqueInstanceStream.Write(new byte[] { 0 }, 0, 1);
                UniqueInstanceStream.Flush();
            }
            catch
            {
                throw new Exception("Another instance of the application is already running.");
            }
        }
    }
}
person A.T.    schedule 20.08.2017

[Ниже я привел пример кода для консольных приложений и приложений WPF.]

Вам нужно только проверить значение переменной createdNew (пример ниже!) После создания именованного экземпляра Mutex.

Логическое значение createdNew вернет false:

если экземпляр Mutex с именем "YourApplicationNameHere" уже был создан где-то в системе

Логическое значение createdNew вернет true:

если это первый мьютекс с именем "YourApplicationNameHere" в системе.


Консольное приложение - пример:

static Mutex m = null;

static void Main(string[] args)
{
    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        using (m = new Mutex(true, mutexName, out createdNew))
        {
            if (!createdNew)
            {
                Console.WriteLine("instance is alreday running... shutting down !!!");
                Console.Read();
                return; // Exit the application
            }

            // Run your windows forms app here
            Console.WriteLine("Single instance app is running!");
            Console.ReadLine();
        }


    }
    catch (Exception ex)
    {

        Console.WriteLine(ex.Message);
        Console.ReadLine();
    }
}

Пример WPF:

public partial class App : Application
{
static Mutex m = null;

protected override void OnStartup(StartupEventArgs e)
{

    const string mutexName = "YourApplicationNameHere";
    bool createdNew = false;

    try
    {
        // Initializes a new instance of the Mutex class with a Boolean value that indicates 
        // whether the calling thread should have initial ownership of the mutex, a string that is the name of the mutex, 
        // and a Boolean value that, when the method returns, indicates whether the calling thread was granted initial ownership of the mutex.

        m = new Mutex(true, mutexName, out createdNew);

        if (!createdNew)
        {
            Current.Shutdown(); // Exit the application
        }

    }
    catch (Exception)
    {
        throw;
    }

    base.OnStartup(e);
}


protected override void OnExit(ExitEventArgs e)
{
    if (m != null)
    {
        m.Dispose();
    }
    base.OnExit(e);
}
}
person Legends    schedule 16.12.2017

Я использую Mutex в мое решение для предотвращения нескольких экземпляров.

static Mutex mutex = null;
//A string that is the name of the mutex
string mutexName = @"Global\test";
//Prevent Multiple Instances of Application
bool onlyInstance = false;
mutex = new Mutex(true, mutexName, out onlyInstance);

if (!onlyInstance)
{
  MessageBox.Show("You are already running this application in your system.", "Already Running..", MessageBoxButton.OK);
  Application.Current.Shutdown();
}
person Vishnu Babu    schedule 22.07.2019

Используйте решение мьютекса:

using System;
using System.Windows.Forms;
using System.Threading;

namespace OneAndOnlyOne
{
static class Program
{
    static String _mutexID = " // generate guid"
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        Boolean _isNotRunning;
        using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
        {
            if (_isNotRunning)
            {
                Application.Run(new Form1());
            }
            else
            {
                MessageBox.Show("An instance is already running.");
                return;
            }
        }
    }
}
}
person Cornel Marian    schedule 19.10.2013

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

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

static readonly string guid = "<Application Guid>";

static void Main()
{
    Mutex mutex = null;
    if (!CreateMutex(out mutex))
        return;

    // Application startup code.

    Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}

static bool CreateMutex(out Mutex mutex)
{
    bool createdNew = false;
    mutex = new Mutex(false, guid, out createdNew);

    if (createdNew)
    {
        Process process = Process.GetCurrentProcess();
        string value = process.Id.ToString();

        Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
    }
    else
    {
        string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
        Process process = null;
        int processId = -1;

        if (int.TryParse(value, out processId))
            process = Process.GetProcessById(processId);

        if (process == null || !SetForegroundWindow(process.MainWindowHandle))
            MessageBox.Show("Unable to start application. An instance of this application is already running.");
    }

    return createdNew;
}

Изменить: вы также можете хранить и инициализировать мьютекс и createdNew статически, но вам нужно будет явно удалить / освободить мьютекс, как только вы закончите с ним. Лично я предпочитаю сохранять мьютекс локальным, поскольку он будет автоматически удален, даже если приложение закроется, не дойдя до конца Main.

person Jason Lim    schedule 29.11.2013

Вы также можете использовать CodeFluent Runtime, который представляет собой бесплатный набор инструментов. Он предоставляет класс SingleInstance для реализации одноэкземплярное приложение.

person Antoine Diekmann    schedule 15.09.2014

Вот то же самое, реализованное через Event.

public enum ApplicationSingleInstanceMode
{
    CurrentUserSession,
    AllSessionsOfCurrentUser,
    Pc
}

public class ApplicationSingleInstancePerUser: IDisposable
{
    private readonly EventWaitHandle _event;

    /// <summary>
    /// Shows if the current instance of ghost is the first
    /// </summary>
    public bool FirstInstance { get; private set; }

    /// <summary>
    /// Initializes 
    /// </summary>
    /// <param name="applicationName">The application name</param>
    /// <param name="mode">The single mode</param>
    public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
    {
        string name;
        if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
            name = $"Local\\{applicationName}";
        else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
            name = $"Global\\{applicationName}{Environment.UserDomainName}";
        else
            name = $"Global\\{applicationName}";

        try
        {
            bool created;
            _event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
            FirstInstance = created;
        }
        catch
        {
        }
    }

    public void Dispose()
    {
        _event.Dispose();
    }
}
person Siarhei Kuchuk    schedule 11.07.2016

Вот как я решил эту проблему. Обратите внимание, что код отладки все еще доступен для тестирования. Этот код находится в OnStartup в файле App.xaml.cs. (WPF)

        // Process already running ? 
        if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
        {

            // Show your error message
            MessageBox.Show("xxx is already running.  \r\n\r\nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);

            // This process 
            Process currentProcess = Process.GetCurrentProcess();

            // Get all processes running on the local computer.
            Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);

            // ID of this process... 
            int temp = currentProcess.Id;
            MessageBox.Show("This Process ID:  " + temp.ToString());

            for (int i = 0; i < localAll.Length; i++)
            {
                // Find the other process 
                if (localAll[i].Id != currentProcess.Id)
                {
                    MessageBox.Show("Original Process ID (Switching to):  " + localAll[i].Id.ToString());

                    // Switch to it... 
                    SetForegroundWindow(localAll[i].MainWindowHandle);

                }
            }

            Application.Current.Shutdown();

        }

Это могут быть проблемы, которых я еще не обнаружил. Если я столкнусь с чем-то, я обновлю свой ответ.

person pStan    schedule 09.08.2016

Решение для экономии времени для C # Winforms ...

Program.cs:

using System;
using System.Windows.Forms;
// needs reference to Microsoft.VisualBasic
using Microsoft.VisualBasic.ApplicationServices;  

namespace YourNamespace
{
    public class SingleInstanceController : WindowsFormsApplicationBase
    {
        public SingleInstanceController()
        {
            this.IsSingleInstance = true;
        }

        protected override void OnStartupNextInstance(StartupNextInstanceEventArgs e)
        {
            e.BringToForeground = true;
            base.OnStartupNextInstance(e);
        }

        protected override void OnCreateMainForm()
        {
            this.MainForm = new Form1();
        }
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            string[] args = Environment.GetCommandLineArgs();
            SingleInstanceController controller = new SingleInstanceController();
            controller.Run(args);
        }
    }
}
person A.J.Bauer    schedule 07.02.2018

Пожалуйста, проверьте предлагаемое решение на странице здесь, который использует семафор, чтобы определить, запущен ли уже существующий экземпляр, работает для приложения WPF и может передавать аргументы из второго экземпляра в первый уже запущенный экземпляр с помощью TcpListener и TcpClient:

Он работает также для .NET Core, а не только для .NET Framework.

person Alexandru Dicu    schedule 22.01.2020

Я не могу найти здесь короткого решения, поэтому надеюсь, что это кому-то понравится:

ОБНОВЛЕНО 20 сентября 2018 г.

Поместите этот код в свой Program.cs:

using System.Diagnostics;

static void Main()
{
    Process thisProcess = Process.GetCurrentProcess();
    Process[] allProcesses = Process.GetProcessesByName(thisProcess.ProcessName);
    if (allProcesses.Length > 1)
    {
        // Don't put a MessageBox in here because the user could spam this MessageBox.
        return;
    }

    // Optional code. If you don't want that someone runs your ".exe" with a different name:

    string exeName = AppDomain.CurrentDomain.FriendlyName;
    // in debug mode, don't forget that you don't use your normal .exe name.
    // Debug uses the .vshost.exe.
    if (exeName != "the name of your executable.exe") 
    {
        // You can add a MessageBox here if you want.
        // To point out to users that the name got changed and maybe what the name should be or something like that^^ 
        MessageBox.Show("The executable name should be \"the name of your executable.exe\"", 
            "Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
        return;
    }

    // Following code is default code:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}
person Deniz    schedule 12.04.2018
comment
Это вызовет состояние гонки. Необходимо использовать мьютекс. - person georgiosd; 17.09.2018
comment
Нет никакой гарантии, что если вы запустите два экземпляра одновременно, это сработает. Как обновление переменной из двух разных потоков. Хитрый рискованный бизнес. Используй силу, Люк :) - person georgiosd; 19.09.2018
comment
@georgiosd а, я понимаю, о чем вы. Например, если кто-то запускает .exe и меняет имя. Да, это был бы способ запустить его несколько раз, но обычно .exe не работает, если имя было изменено. Я обновлю свой ответ ^^ Спасибо, Люк: D, за указание на это :) - person Deniz; 20.09.2018
comment
Не только это @Deniz. Если вы запустите два процесса очень быстро, есть вероятность, что список процессов или метод, их извлекающий, будут выполняться, пока отображается только один. Это может быть крайний случай, который не имеет отношения к вам, но это общий вопрос ... - person georgiosd; 26.09.2018
comment
@georgiosd Можете ли вы это доказать? Потому что я тестировал это только для тебя, хе-хе. Но для меня это было невозможно, даже очень-очень быстро! : P Итак, я не могу понять, почему вы верите в то, что не так, и даже не любите этот невинный код: D - person Deniz; 28.09.2018
comment
Мне не нужно это доказывать @Deniz, это данность в информатике для многопоточных систем. Условия гонки Google. - person georgiosd; 01.10.2018
comment
@georgiosd Как я уже говорил. Я пробовал запускать его несколько раз. Это никак не могло сработать (по крайней мере, для меня). Так что было бы неплохо, если бы вы это доказали. В противном случае вы будете как бы недоброжелательны: / - person Deniz; 01.10.2018

Обычно это код, который я использую для одноэкземплярных приложений Windows Forms:

[STAThread]
public static void Main()
{
    String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;

    using (Mutex mutex = new Mutex(false, assemblyName))
    {
        if (!mutex.WaitOne(0, false))
        {
            Boolean shownProcess = false;
            Process currentProcess = Process.GetCurrentProcess();

            foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
            {
                if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
                {
                    IntPtr windowHandle = process.MainWindowHandle;

                    if (NativeMethods.IsIconic(windowHandle))
                        NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);

                    NativeMethods.SetForegroundWindow(windowHandle);

                    shownProcess = true;
                }
            }

            if (!shownProcess)
                MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
        }
        else
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form());
        }
    }
}

Где есть собственные компоненты:

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);

[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);

public enum ShowWindowCommand : int
{
    Hide                   = 0x0,
    ShowNormal             = 0x1,
    ShowMinimized          = 0x2,
    ShowMaximized          = 0x3,
    ShowNormalNotActive    = 0x4,
    Minimize               = 0x6,
    ShowMinimizedNotActive = 0x7,
    ShowCurrentNotActive   = 0x8,
    Restore                = 0x9,
    ShowDefault            = 0xA,
    ForceMinimize          = 0xB
}
person Tommaso Belluzzo    schedule 15.01.2013
comment
Проблема этой реализации заключается в том, что вы не можете предоставить аргументы командной строки из второго экземпляра обратно в первый. Более подробное объяснение можно найти здесь. - person Oliver; 15.01.2013
comment
Похоже, вопрос не требует этого. В любом случае, это не будет согласованным поведением ... завершаемый экземпляр не должен изменять поведение существующего. Если вы хотите, чтобы ваше приложение вело себя по-другому, вы закрываете текущий процесс и запускаете новый с другими параметрами. - person Tommaso Belluzzo; 22.04.2017
comment
Но офис работает по умолчанию именно так. Вы открываете первый документ двойным щелчком, и начинается новый процесс. Вы открываете второй документ, и он получает окно в первом экземпляре. - person Oliver; 24.04.2017
comment
Я все еще не понимаю. Это не запрашиваемая функция. - person Tommaso Belluzzo; 24.04.2017
comment
Это было. Последний абзац вопроса: Код должен также сообщить уже запущенному экземпляру, что пользователь попытался запустить второй, и возможно также передать какие-либо аргументы командной строки, если таковые существуют. - person Oliver; 24.04.2017

Вот решение:

Protected Overrides Sub OnStartup(e As StartupEventArgs)
    Const appName As String = "TestApp"
    Dim createdNew As Boolean
    _mutex = New Mutex(True, appName, createdNew)
    If Not createdNew Then
        'app is already running! Exiting the application
        MessageBox.Show("Application is already running.")
        Application.Current.Shutdown()
    End If
    MyBase.OnStartup(e)
End Sub
person Code Scratcher    schedule 05.07.2016
comment
Мне нравятся простые решения, поэтому я сначала попробовал это ... не смог заставить его работать. - person pStan; 09.08.2016

Вот мои 2 цента

 static class Program
    {
        [STAThread]
        static void Main()
        {
            bool createdNew;
            using (new Mutex(true, "MyApp", out createdNew))
            {
                if (createdNew) {
                    Application.EnableVisualStyles();
                    Application.SetCompatibleTextRenderingDefault(false);
                    var mainClass = new SynGesturesLogic();
                    Application.ApplicationExit += mainClass.tray_exit;
                    Application.Run();
                }
                else
                {
                    var current = Process.GetCurrentProcess();
                    foreach (var process in Process.GetProcessesByName(current.ProcessName).Where(process => process.Id != current.Id))
                    {
                        NativeMethods.SetForegroundWindow(process.MainWindowHandle);
                        break;
                    }
                }
            }
        }
    }
person kakopappa    schedule 26.08.2016
comment
Что такое класс NativeMethods? - person ufo; 15.12.2016

Мне нравится решение, позволяющее использовать несколько экземпляров, если exe вызывается с другого пути. Я модифицировал метод 1 решения CharithJ:

   static class Program {
    [DllImport("user32.dll")]
    private static extern bool ShowWindow(IntPtr hWnd, Int32 nCmdShow);
    [DllImport("User32.dll")]
    public static extern Int32 SetForegroundWindow(IntPtr hWnd);
    [STAThread]
    static void Main() {
        Process currentProcess = Process.GetCurrentProcess();
        foreach (var process in Process.GetProcesses()) {
            try {
                if ((process.Id != currentProcess.Id) && 
                    (process.ProcessName == currentProcess.ProcessName) &&
                    (process.MainModule.FileName == currentProcess.MainModule.FileName)) {
                    ShowWindow(process.MainWindowHandle, 5); // const int SW_SHOW = 5; //Activates the window and displays it in its current size and position. 
                    SetForegroundWindow(process.MainWindowHandle);
                    return;
                }
            } catch (Exception ex) {
                //ignore Exception "Access denied "
            }
        }

        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
person Pete    schedule 29.09.2016

Просто используя StreamWriter, как насчет этого?

System.IO.File.StreamWriter OpenFlag = null;   //globally

а также

try
{
    OpenFlag = new StreamWriter(Path.GetTempPath() + "OpenedIfRunning");
}
catch (System.IO.IOException) //file in use
{
    Environment.Exit(0);
}
person Divins Mathew    schedule 11.01.2017

Мое любимое решение от MVP Daniel Vaughan: Применение приложений Wpf с одним экземпляром

Он использует MemoryMappedFile для отправки аргументов командной строки первому экземпляру:

/// <summary>
/// This class allows restricting the number of executables in execution, to one.
/// </summary>
public sealed class SingletonApplicationEnforcer
{
    readonly Action<IEnumerable<string>> processArgsFunc;
    readonly string applicationId;
    Thread thread;
    string argDelimiter = "_;;_";

    /// <summary>
    /// Gets or sets the string that is used to join 
    /// the string array of arguments in memory.
    /// </summary>
    /// <value>The arg delimeter.</value>
    public string ArgDelimeter
    {
        get
        {
            return argDelimiter;
        }
        set
        {
            argDelimiter = value;
        }
    }

    /// <summary>
    /// Initializes a new instance of the <see cref="SingletonApplicationEnforcer"/> class.
    /// </summary>
    /// <param name="processArgsFunc">A handler for processing command line args 
    /// when they are received from another application instance.</param>
    /// <param name="applicationId">The application id used 
    /// for naming the <seealso cref="EventWaitHandle"/>.</param>
    public SingletonApplicationEnforcer(Action<IEnumerable<string>> processArgsFunc, 
        string applicationId = "DisciplesRock")
    {
        if (processArgsFunc == null)
        {
            throw new ArgumentNullException("processArgsFunc");
        }
        this.processArgsFunc = processArgsFunc;
        this.applicationId = applicationId;
    }

    /// <summary>
    /// Determines if this application instance is not the singleton instance.
    /// If this application is not the singleton, then it should exit.
    /// </summary>
    /// <returns><c>true</c> if the application should shutdown, 
    /// otherwise <c>false</c>.</returns>
    public bool ShouldApplicationExit()
    {
        bool createdNew;
        string argsWaitHandleName = "ArgsWaitHandle_" + applicationId;
        string memoryFileName = "ArgFile_" + applicationId;

        EventWaitHandle argsWaitHandle = new EventWaitHandle(
            false, EventResetMode.AutoReset, argsWaitHandleName, out createdNew);

        GC.KeepAlive(argsWaitHandle);

        if (createdNew)
        {
            /* This is the main, or singleton application. 
                * A thread is created to service the MemoryMappedFile. 
                * We repeatedly examine this file each time the argsWaitHandle 
                * is Set by a non-singleton application instance. */
            thread = new Thread(() =>
                {
                    try
                    {
                        using (MemoryMappedFile file = MemoryMappedFile.CreateOrOpen(memoryFileName, 10000))
                        {
                            while (true)
                            {
                                argsWaitHandle.WaitOne();
                                using (MemoryMappedViewStream stream = file.CreateViewStream())
                                {
                                    var reader = new BinaryReader(stream);
                                    string args;
                                    try
                                    {
                                        args = reader.ReadString();
                                    }
                                    catch (Exception ex)
                                    {
                                        Debug.WriteLine("Unable to retrieve string. " + ex);
                                        continue;
                                    }
                                    string[] argsSplit = args.Split(new string[] { argDelimiter }, 
                                                                    StringSplitOptions.RemoveEmptyEntries);
                                    processArgsFunc(argsSplit);
                                }

                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine("Unable to monitor memory file. " + ex);
                    }
                });

            thread.IsBackground = true;
            thread.Start();
        }
        else
        {
            /* Non singleton application instance. 
                * Should exit, after passing command line args to singleton process, 
                * via the MemoryMappedFile. */
            using (MemoryMappedFile mmf = MemoryMappedFile.OpenExisting(memoryFileName))
            {
                using (MemoryMappedViewStream stream = mmf.CreateViewStream())
                {
                    var writer = new BinaryWriter(stream);
                    string[] args = Environment.GetCommandLineArgs();
                    string joined = string.Join(argDelimiter, args);
                    writer.Write(joined);
                }
            }
            argsWaitHandle.Set();
        }

        return !createdNew;
    }
}
person std8590    schedule 03.07.2020

На основе ответа Мэтта Дэвиса, заключенного в класс для удобства.

public static class SingleAppInstanceChecker
{
    /// <summary>
    /// Arbitrary unique string
    /// </summary>
    private static Mutex _mutex = new Mutex(true, "0d12ad74-026f-40c3-bdae-e178ddee8602");

    public static bool IsNotRunning()
    {
        return _mutex.WaitOne(TimeSpan.Zero, true);
    }
}

Пример использования:

private void Application_Startup(object sender, StartupEventArgs e)
{
    if (!SingleAppInstanceChecker.IsNotRunning())
    {
        MessageBox.Show("Application is already running.");
        Environment.Exit(1);
        return;
    }
    
    // Allow startup and continue with normal processing
    // ...
}
person datchung    schedule 01.07.2021