Подтвердить что ты не робот

Рекомендации WPF по одному экземпляру

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

#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion

namespace MyWPF
{
    public partial class MainApplication : Application, IDisposable
    {
        #region Members
        private Int32 m_Message;
        private Mutex m_Mutex;
        #endregion

        #region Methods: Functions
        private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
        {
            if (message == m_Message)
            {
                if (MainWindow.WindowState == WindowState.Minimized)
                    MainWindow.WindowState = WindowState.Normal;

                Boolean topmost = MainWindow.Topmost;

                MainWindow.Topmost = true;
                MainWindow.Topmost = topmost;
            }

            return IntPtr.Zero;
        }

        private void Dispose(Boolean disposing)
        {
            if (disposing && (m_Mutex != null))
            {
                m_Mutex.ReleaseMutex();
                m_Mutex.Close();
                m_Mutex = null;
            }
        }

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

        #region Methods: Overrides
        protected override void OnStartup(StartupEventArgs e)
        {
            Assembly assembly = Assembly.GetExecutingAssembly();
            Boolean mutexCreated;
            String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", assembly.GetType().GUID, assembly.GetName().Name);

            m_Mutex = new Mutex(true, mutexName, out mutexCreated);
            m_Message = NativeMethods.RegisterWindowMessage(mutexName);

            if (!mutexCreated)
            {
                m_Mutex = null;

                NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);

                Current.Shutdown();

                return;
            }

            base.OnStartup(e);

            MainWindow window = new MainWindow();
            MainWindow = window;
            window.Show(); 

            HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
        }

        protected override void OnExit(ExitEventArgs e)
        {
            Dispose();
            base.OnExit(e);
        }
        #endregion
    }
}

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

1) Мне был задан Code Analysis для реализации интерфейса IDisposable, потому что я использовал членов IDisposable (Mutex). Является ли моя реализация Dispose() достаточно хорошей? Должен ли я избегать этого, потому что его никогда не назовут?

2) Лучше использовать m_Mutex = new Mutex(true, mutexName, out mutexCreated); и проверить результат или использовать m_Mutex = new Mutex(false, mutexName);, а затем проверить m_Mutex.WaitOne(TimeSpan.Zero, false);? В случае многопоточности я имею в виду...

3) RegisterWindowMessage Запрос API должен возвращать UInt32... но HwndSourceHook принимает только Int32 как значение сообщения... я должен беспокоиться о неожиданном поведении (например, результат больше, чем Int32.MaxValue)?

4) В OnStartup переопределить... должен ли я выполнить base.OnStartup(e);, даже если другой экземпляр уже запущен, и я закрою приложение?

5) Есть ли лучший способ довести существующий экземпляр до вершины, которому не нужно устанавливать значение Topmost? Может быть, Activate()?

6) Вы видите какой-то недостаток в моем подходе? Что-то касающееся многопоточности, неправильной обработки исключений и что-то в этом роде? Например... что произойдет, если мое приложение выйдет из строя между OnStartup и OnExit?

4b9b3361

Ответ 1

1) Он выглядит как стандартная реализация Dispose для меня. Это не обязательно (см. Пункт 6), но это не наносит вреда. (Очистка при закрытии его немного напоминает очистку дома перед тем, как сжечь его, ИМХО, но мнения по этому вопросу отличаются.)

В любом случае, почему бы не использовать "Dispose" в качестве имени метода очистки, даже если он не вызван напрямую? Вы могли бы назвать его "Очистка", но помните, что вы также пишете код для людей, а Dispose выглядит знакомым, а кто-то из .NET понимает, для чего он нужен. Итак, перейдите на "Dispose".

2) Я всегда видел m_Mutex = new Mutex(false, mutexName); Я думаю, что это скорее соглашение о техническом преимуществе.

3) Из MSDN:

Если сообщение успешно зарегистрировано, возвращаемое значение является идентификатором сообщения в диапазоне от 0xC000 до 0xFFFF.

Так что я не стал бы волноваться. Обычно для этого класса функций UInt не используется для "он не вписывается в Int, пусть использует UInt, поэтому у нас есть что-то большее, но для уточнения контракта" функция никогда не возвращает отрицательное значение ".

4) Я бы не позвонил, если вы остановитесь, по той же причине, что и # 1

5) Есть несколько способов сделать это. Самый простой способ в Win32 - просто заставить второй экземпляр сделать вызов SetForegroundWindow (смотрите здесь: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx); однако, я не знаю, есть ли эквивалентная функциональность WPF или если вам нужно PInvoke.

6)

Например... что произойдет, если мое приложение выйдет из строя между OnStartup и OnExit?

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

Короче говоря, мои рекомендации:

  • Я бы использовал подход, основанный на именованных объектах синхронизации: он более определен на платформах Windows. (Будьте внимательны при рассмотрении многопользовательской системы, такой как сервер терминалов! Назовите объект синхронизации как комбинацию, возможно, имя пользователя /SID и имя приложения)
  • Используйте Windows API для поднять предыдущий экземпляр (см. мою ссылку в пункте № 5) или эквивалент WPF.
  • Вам, вероятно, не нужно беспокоиться о сбоях (ядро уменьшит счетчик ссылок для объекта ядра для вас, сделайте небольшой тест в любом случае), НО Если я могу предложить улучшение: что, если ваш первый экземпляр приложения не падает но висит? (Происходит с Firefox.. Я уверен, что это случилось и с вами! Нет окна, ff процесс, вы не можете открыть новый). В этом случае может быть полезно совместить другой метод или два: a) проверить, отвечает ли приложение/окно; б) найти замеченный экземпляр и прервать его

Например, вы можете использовать свою технику (пытаясь отправить/отправить сообщение в окно - если она не ответит, она застряла), а также технику MSK, чтобы найти и завершить старый процесс. Затем начните нормально.

Ответ 2

Есть несколько вариантов,

  • Mutex
  • Менеджер процессов
  • Именованный семафор
  • Используйте сокет слушателя

Mutex

Mutex myMutex ;

private void Application_Startup(object sender, StartupEventArgs e)
{
    bool aIsNewInstance = false;
    myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);  
    if (!aIsNewInstance)
    {
        MessageBox.Show("Already an instance is running...");
        App.Current.Shutdown();  
    }
}

Менеджер процессов

private void Application_Startup(object sender, StartupEventArgs e)
{
    Process proc = Process.GetCurrentProcess();
    int count = Process.GetProcesses().Where(p=> 
        p.ProcessName == proc.ProcessName).Count();

    if (count > 1)
    {
        MessageBox.Show("Already an instance is running...");
        App.Current.Shutdown(); 
    }
}

Используйте сокет слушателя

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

"Безопасность" программного обеспечения/брандмауэров может быть проблемой.

Приложение для одного экземпляра С#.Net вместе с Win32

Ответ 3

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

Я использую named Mutex для обеспечения того, чтобы выполнялся только один экземпляр, именовал EventWaitHandle для передачи уведомлений из одного экземпляра в другой.

App.xaml.cs:

/// <summary>Interaction logic for App.xaml</summary>
public partial class App
{
    #region Constants and Fields

    /// <summary>The event mutex name.</summary>
    private const string UniqueEventName = "{GUID}";

    /// <summary>The unique mutex name.</summary>
    private const string UniqueMutexName = "{GUID}";

    /// <summary>The event wait handle.</summary>
    private EventWaitHandle eventWaitHandle;

    /// <summary>The mutex.</summary>
    private Mutex mutex;

    #endregion

    #region Methods

    /// <summary>The app on startup.</summary>
    /// <param name="sender">The sender.</param>
    /// <param name="e">The e.</param>
    private void AppOnStartup(object sender, StartupEventArgs e)
    {
        bool isOwned;
        this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
        this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);

        // So, R# would not give a warning that this variable is not used.
        GC.KeepAlive(this.mutex);

        if (isOwned)
        {
            // Spawn a thread which will be waiting for our event
            var thread = new Thread(
                () =>
                {
                    while (this.eventWaitHandle.WaitOne())
                    {
                        Current.Dispatcher.BeginInvoke(
                            (Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
                    }
                });

            // It is important mark it as background otherwise it will prevent app from exiting.
            thread.IsBackground = true;

            thread.Start();
            return;
        }

        // Notify other instance so it could bring itself to foreground.
        this.eventWaitHandle.Set();

        // Terminate this instance.
        this.Shutdown();
    }

    #endregion
}

И BringToForeground в MainWindow.cs:

    /// <summary>Brings main window to foreground.</summary>
    public void BringToForeground()
    {
        if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
        {
            this.Show();
            this.WindowState = WindowState.Normal;
        }

        // According to some sources these steps gurantee that an app will be brought to foreground.
        this.Activate();
        this.Topmost = true;
        this.Topmost = false;
        this.Focus();
    }

И добавьте Startup = "AppOnStartup" (спасибо vhanla!):

<Application x:Class="MyClass.App"  
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"   
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             Startup="AppOnStartup">
    <Application.Resources>
    </Application.Resources>
</Application>

Работает для меня:)

Ответ 4

Для WPF просто используйте:

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

    protected override void OnStartup(StartupEventArgs e)
    {
        const string appName = "MyAppName";
        bool createdNew;

        _mutex = new Mutex(true, appName, out createdNew);

        if (!createdNew)
        {
            //app is already running! Exiting the application  
            Application.Current.Shutdown();
        }

        base.OnStartup(e);
    }          
}

Ответ 5

Самый простой способ справиться с этим будет с помощью семафора с именем. Попробуйте что-то вроде этого...

public partial class App : Application
{
    Semaphore sema;
    bool shouldRelease = false;

    protected override void OnStartup(StartupEventArgs e)
    {

        bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);

        if (result) // we have another instance running
        {
            App.Current.Shutdown();
        }
        else
        {
            try
            {
                sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
            }
            catch
            {
                App.Current.Shutdown(); //
            }
        }

        if (!sema.WaitOne(0))
        {
            App.Current.Shutdown();
        }
        else
        {
            shouldRelease = true;
        }


        base.OnStartup(e);
    }

    protected override void OnExit(ExitEventArgs e)
    {
        if (sema != null && shouldRelease)
        {
            sema.Release();
        }
    }

}

Ответ 6

Я использовал простой TCP-сокет для этого (в Java, 10 лет назад).

  • При запуске подключиться к предопределенному порту, если соединение принято, выполняется другой экземпляр, если нет, запустите прослушиватель TCP
  • Как только кто-то подключится к вам, всплывающее окно и отключите

Ответ 7

чтобы предотвратить второй случай,

  • используя EventWaitHandle (так как мы говорим о событии),
  • используя задачу,
  • код Mutex не требуется,
  • нет TCP,
  • никаких пинвоков,
  • никаких сборщиков мусора,
  • сохранить поток

это можно сделать следующим образом (это для приложения WPF (см. ref для App()), но также работает на WinForms):

public partial class App : Application
{
    public App()
    {
        // initiate it. Call it first.
        preventSecond();
    }

    private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";

    private void preventSecond()
    {
        try
        {
            EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
            this.Shutdown();
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
        }
    }
}

Вторая версия: выше плюс сигнализация другого экземпляра для отображения окна (измените часть MainWindow для WinForms):

public partial class App : Application
{
    public App()
    {
        // initiate it. Call it first.
        //preventSecond();
        SingleInstanceWatcher();
    }

    private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
    private EventWaitHandle eventWaitHandle;

    /// <summary>prevent a second instance and signal it to bring its mainwindow to foregorund</summary>
    /// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
    private void SingleInstanceWatcher()
    {
        // check if it is allready open.
        try
        {
            // try to open it - if another instance is running, it will exist
            this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);

            // Notify other instance so it could bring itself to foreground.
            this.eventWaitHandle.Set();

            // Terminate this instance.
            this.Shutdown();
        }
        catch (WaitHandleCannotBeOpenedException)
        {
            // listen to a new event
            this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
        }

        // if this instance gets the signal to show the main window
        new Task(() =>
        {
            while (this.eventWaitHandle.WaitOne())
            {
                Current.Dispatcher.BeginInvoke((Action)(() =>
                {
                    // could be set or removed anytime
                    if (!Current.MainWindow.Equals(null))
                    {
                        var mw = Current.MainWindow;

                        if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
                        {
                            mw.Show();
                            mw.WindowState = WindowState.Normal;
                        }

                        // According to some sources these steps gurantee that an app will be brought to foreground.
                        mw.Activate();
                        mw.Topmost = true;
                        mw.Topmost = false;
                        mw.Focus();
                    }
                }));
            }
        })
        .Start();
    }
}

Этот код, как капля в классе, будет @Selfcontained-C-Sharp-WPF-совместимая утилита-classes / Utils.SingleInstance.cs

Ответ 8

Это простое решение, Откройте файл запуска (вид, с которого начинается ваше приложение), в этом случае его MainWindow.xaml. Откройте файл MainWindow.xaml.cs. Перейдите к конструктору и после intializecomponent() добавьте этот код:

Process Currentproc = Process.GetCurrentProcess();

Process[] procByName=Process.GetProcessesByName("notepad");  //Write the name of your exe file in inverted commas
if(procByName.Length>1)
{
  MessageBox.Show("Application is already running");
  App.Current.Shutdown();
 }

Не забудьте добавить System.Diagnostics

Ответ 9

Вот пример, который также возвращает старый экземпляр на передний план:

public partial class App : Application
{
    [DllImport("user32", CharSet = CharSet.Unicode)]
    static extern IntPtr FindWindow(string cls, string win);
    [DllImport("user32")]
    static extern IntPtr SetForegroundWindow(IntPtr hWnd);
    [DllImport("user32")]
    static extern bool IsIconic(IntPtr hWnd);
    [DllImport("user32")]
    static extern bool OpenIcon(IntPtr hWnd);

    private static Mutex _mutex = null;

    protected override void OnStartup(StartupEventArgs e)
    {
        const string appName = "LinkManager";
        bool createdNew;

        _mutex = new Mutex(true, appName, out createdNew);

        if (!createdNew)
        {
            ActivateOtherWindow();
            //app is already running! Exiting the application  
            Application.Current.Shutdown();
        }

        base.OnStartup(e);
    }

    private static void ActivateOtherWindow()
    {
        var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
        if (other != IntPtr.Zero)
        {
            SetForegroundWindow(other);
            if (IsIconic(other))
                OpenIcon(other);
        }
    }
}

Но это будет работать только в том случае, если заголовок основного окна не изменит время выполнения durig.

Edit:

Вы также можете использовать событие Startup в App.xaml вместо переопределения OnStartup.

// App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
    const string appName = "LinkManager";
    bool createdNew;

    _mutex = new Mutex(true, appName, out createdNew);

    if (!createdNew)
    {
        ActivateOtherWindow();
        //app is already running! Exiting the application  
        Application.Current.Shutdown();
    }
}

// App.xaml
<Application x:Class="MyApp.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:MyApp"
         StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event

Не забудьте в этом случае не называть base.OnStartup(e)!