programing

WPF 싱글인스턴스의 베스트프랙티스

powerit 2023. 4. 19. 00:31
반응형

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)를 5를 없는 맨 수 더 요?Topmost 치? value? 아마도Activate()

? 6) 의의 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6?멀티스레딩이나 나쁜 예외 처리 같은 거요?★★★★★★★★★★…가 '어플리케이션 크래쉬' 사이에 됩니까?OnStartup ★★★★★★★★★★★★★★★★★」OnExit

몇 가지 선택지가 있습니다.

  • 뮤텍스
  • 프로세스 매니저
  • 명명된 세마포어
  • 수신기 소켓 사용

뮤텍스

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 접속을 여는 것입니다.소켓을 만들고 포트에 바인드하여 백그라운드스레드로 접속을 리슨합니다.성공하면 정상적으로 실행해 주세요.그렇지 않은 경우 해당 포트에 연결합니다.이 포트는 두 번째 응용 프로그램 부팅 시도가 이루어졌음을 다른 인스턴스에 알립니다.그런 다음 원래 인스턴스는 필요에 따라 주 창을 앞으로 가져올 수 있습니다.

"보안" 소프트웨어/방화벽이 문제가 될 수 있습니다.

싱글 인스턴스 애플리케이션 C#.Win32와 함께 인터넷 접속

조금 더 나은 사용자 경험을 원했습니다. 다른 인스턴스가 이미 실행 중이라면 두 번째 인스턴스에 대한 오류를 표시하지 말고 활성화해 봅시다.제 실장은 다음과 같습니다.

하나의 인스턴스만 실행 중인지 확인하기 위해 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
}

MainWindow.cs의 BringToForground:

    /// <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>

나에게 효과가 있다:)

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);
    }          
}

두 번째 인스턴스(및 기존 인스턴스 신호)를 방지하려면

  • EventWaitHandle을 사용합니다(이벤트에 대해 이야기하고 있기 때문에),
  • 작업 사용,
  • 뮤텍스 코드는 필요 없습니다.
  • TCP 없음,
  • Pinvokes 없음,
  • Garbage Collection은 없습니다.
  • 스레드 세이브
  • 간단하죠.

다음과 같이 할 수 있습니다(WPF 앱의 경우(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
        }
    }
}

두 번째 버전: 위의 다른 인스턴스에 시그널링을 추가하여 창을 표시합니다(WinForms의 MainWindow 부분을 변경).

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 foreground</summary>
    /// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
    private void SingleInstanceWatcher()
    {
        // check if it is already open.
        try
        {
            // try to open it - if another instance is running, it will exist , if not it will throw
            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 app instance will be the new "master")
            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 are required to be sure it went to foreground.
                        mw.Activate();
                        mw.Topmost = true;
                        mw.Topmost = false;
                        mw.Focus();
                    }
                }));
            }
        })
        .Start();
    }
}

이 코드는 클래스 드롭으로서 @ Selfcontained-C-Sharp-WPF 호환 유틸리티 클래스 / Utils 가 됩니다.SingleInstance.cs

1) 표준 Dispose 구현처럼 보입니다.꼭 필요한 것은 아니지만(6점 참조) 해가 되는 것은 아닙니다.(폐쇄시 청소는 소각 전 청소와 비슷하지만, IMHO는 의견이 다릅니다.)

어쨌든, 직접 호출되지 않더라도 정리 방법의 이름으로 「Dispose」를 사용하는 것은 어떻습니까?"Cleanup"이라고 부를 수도 있지만, 인간을 위한 코드도 작성했다는 것을 기억하십시오. Dispose는 익숙한 사람이고, 위에 있는 모든 사람이 있습니다.NET은 그 목적을 이해하고 있습니다.그러니까 'Dispose'로 해주세요.

2) 2) 는는항 2 2 2 2 2 2 2 2 2m_Mutex = new Mutex(false, mutexName);하지만 기술적 이점보다는 관례에 가깝다고 생각합니다.

3) MSDN에서:

메시지가 정상적으로 등록되면 반환값은 0xC000 ~0xFFFF 범위의 메시지 ID가 됩니다.

그래서 나는 걱정하지 않을 것이다.보통 이 함수 클래스의 경우 UInt는 "Int에 맞지 않습니다. UInt를 사용하여 더 많은 것을 얻읍시다"가 아니라 "함수는 음의 값을 반환하지 않습니다"라는 계약을 명확히 하기 위해 사용됩니다.

4) #1과 같은 이유로 셧다운을 하면 전화를 하지 않도록 하겠습니다.

5) 몇 가지 방법이 있습니다.Win32에서 가장 쉬운 방법은 단순히 두 번째 인스턴스가 SetForeground Window로 콜을 발신하는 것입니다(여기서 http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx);를 참조해 주세요.WPF와 동등한 기능이 있는지, PInvoking을 할 필요가 있는지 모르겠습니다.

6)

예를 들면...OnStartup과 OnExit 사이에서 응용 프로그램이 충돌하면 어떻게 됩니까?

괜찮습니다. 프로세스가 종료되면 프로세스가 소유한 모든 핸들이 해제되고 뮤텍스도 해제됩니다.

요컨대, 제 추천은 다음과 같습니다.

  • 명명된 동기화 개체를 기반으로 하는 접근 방식을 사용합니다. 즉, Windows 플랫폼 상에서 더 확립되어 있습니다.(단말기 서버 등의 멀티 유저 시스템을 검토할 때는 주의해 주세요.동기화 개체의 이름을 사용자 이름/SID와 애플리케이션 이름의 조합으로 지정합니다.)
  • Windows API를 사용하여 이전 인스턴스(포인트 #5의 링크 참조) 또는 동등한 WPF를 만듭니다.
  • 크래시에 대해 걱정할 필요는 없습니다(커널은 커널 객체의 참조 카운터를 줄여줍니다.어쨌든 약간의 테스트를 실시합니다).그러나 개선을 제안하고 싶은 경우: 첫 번째 애플리케이션인스턴스가 크래시하지 않고 정지하면 어떻게 합니까?(Firefox의 경우)너도 그랬을 거야!창이 없습니다. 프로세스, 새 창을 열 수 없습니다.)이 경우 다른 기술을 조합하여 a) 어플리케이션/윈도가 응답하는지 테스트하고 b) 행된 인스턴스를 찾아 종료하는 것이 좋습니다.

예를 들어, 기존 프로세스를 검색하여 종료하기 위해 MSK 기술을 사용하여(메시지를 창에 전송/게시하려고 시도합니다.응답하지 않을 경우 메시지가 스택됩니다.) 사용할 수 있습니다.그럼 정상적으로 시작하세요.

가장 간단한 방법은 명명된 세마포를 사용하는 것입니다.이런 거 해봐...

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();
        }
    }

}

용 마이솔루션Net Core 3 WPF 싱글인스턴스 애플리케이션:

[STAThread]
public static void Main()
{
    StartSingleInstanceApplication<CntApplication>();
}

public static void StartSingleInstanceApplication<T>()
    where T : RichApplication
{
    DebuggerOutput.GetInstance();

    Assembly assembly = typeof(T).Assembly;
    string mutexName = $"SingleInstanceApplication/{assembly.GetName().Name}/{assembly.GetType().GUID}";

    Mutex mutex = new Mutex(true, mutexName, out bool mutexCreated);

    if (!mutexCreated)
    {
        mutex = null;

        var client = new NamedPipeClientStream(mutexName);
        client.Connect();

        using (StreamWriter writer = new StreamWriter(client))
            writer.Write(string.Join("\t", Environment.GetCommandLineArgs()));

        return;
    }
    else
    {
        T application = Activator.CreateInstance<T>();

        application.Exit += (object sender, ExitEventArgs e) =>
        {
            mutex.ReleaseMutex();
            mutex.Close();
            mutex = null;
        };

        Task.Factory.StartNew(() =>
        {
            while (mutex != null)
            {
                using (var server = new NamedPipeServerStream(mutexName))
                {
                    server.WaitForConnection();

                    using (StreamReader reader = new StreamReader(server))
                    {
                        string[] args = reader.ReadToEnd().Split("\t", StringSplitOptions.RemoveEmptyEntries).ToArray();
                        UIDispatcher.GetInstance().Invoke(() => application.ExecuteCommandLineArgs(args));
                    }
                }
            }
        }, TaskCreationOptions.LongRunning);

        typeof(T).GetMethod("InitializeComponent").Invoke(application, new object[] { });
        application.Run();
    }
}

저는 이것에 단순한 TCP 소켓을 사용했습니다(Java, 10년 전).

  1. 사전 정의된 포트에 대한 연결 시작 시 연결이 허용되면 다른 인스턴스가 실행되고, 그렇지 않으면 TCP Listener를 시작합니다.
  2. 다른 사용자가 사용자에게 연결되면 창을 팝업하고 연결을 끊습니다.

이것은 간단한 해결책입니다.이 경우 MainWindow.xaml의 스타트업 파일(어플리케이션의 시작 위치에서 표시)을 엽니다.MainWindow.xaml.cs 파일을 엽니다.컨스트럭터로 이동하여 intialize component() 후에 다음 코드를 추가합니다.

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();
 }

시스템 추가 잊지 마세요.진단

다음은 오래된 인스턴스도 포그라운드로 표시하는 예입니다.

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);
        }
    }
}

그러나 기본 창 제목이 실행 시 변경되지 않는 경우에만 작동합니다.

편집:

이 경우에도 하실 수 있습니다.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)★★★★★★★★★★★★★★★★!

내 모자를 여기 링에 던지고 있어.가 하는 은 '만들기'를 예요.ApplicationBase Application모든 WPF 어플리케이션에서 사용하는 공통 라이브러리에 보관하는 클래스입니다.그런 다음 기본 클래스를 사용하도록 기본 클래스(XAML 내부 및 코드 배후에 있음)를 변경합니다.하는 ★★★★★★★★★★★★★EntryPoint.Main앱의 시작 개체로 지정합니다. 그런 다음 단일 인스턴스 상태를 확인하고, 처음의 인스턴스가 아닌 경우만 되돌립니다.

주의: 다른 인스턴스를 시작할 때 플래그를 덮어쓸 수 있는 플래그를 지원하는 방법도 보여 줍니다.단, 이러한 옵션은 주의해 주십시오.말이 되는 곳에서만 사용하세요.

코드는 다음과 같습니다.

ApplicationBase(어플리케이션 서브클래스)

public abstract class ApplicationBase : Application {

    public static string? SingleInstanceId { get; private set; }

    public static bool InitializeAsFirstInstance(string singleInstanceId){

        if(SingleInstanceId != null)
            throw new AlreadyInitializedException(singleInstanceId);

        SingleInstanceId = singleInstanceId;

        var waitHandleName = $"SingleInstanceWaitHandle:{singleInstanceId}";

        if(EventWaitHandle.TryOpenExisting(waitHandleName, out var waitHandle)){

            // An existing WaitHandle was successfuly opened which means we aren't the first so signal the other
            waitHandle.Set();

            // Then indicate we aren't the first instance by returning false
            return false;
        }

        // Welp, there was no existing WaitHandle with this name, so we're the first!
        // Now we have to set up the EventWaitHandle in a task to listen for other attempts to launch

        void taskBody(){

            var singleInstanceWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, waitHandleName);

            while (singleInstanceWaitHandle.WaitOne()) {

                if(Current is ApplicationBase applicationBase)
                    Current.Dispatcher.BeginInvoke(applicationBase.OtherInstanceLaunched);
            }
        }

        new Task(taskBody, TaskCreationOptions.LongRunning).Start();

        return true;
    }

    public static bool IsSingleInstance
        => SingleInstanceId != null;

    protected virtual void OtherInstanceLaunched()
        => Current.MainWindow?.BringToFront();
}

「」마크를 OtherInstanceLaunched 할 수 의 입니다.Window) 라고 .

엔트리 포인트주된

public static class EntryPoint {

    public static class CommandLineArgs{
        public const string AllowMulti = "/AllowMulti";
        public const string NoSplash   = "/NoSplash";
    }

    [STAThread]
    public static int Main(string[] args) {

        var showSplashScreen = true;
        var allowMulti       = false;

        foreach (var arg in args) {

            if (arg.Equals(CommandLineArgs.AllowMulti, StringComparison.CurrentCultureIgnoreCase))
                allowMulti = true;

            if (arg.Equals(CommandLineArgs.NoSplash, StringComparison.CurrentCultureIgnoreCase))
                showSplashScreen = false;
        }

        // Try and initialize myself as the first instance. If I'm not and 'allowMulti' is false, exit with a return code of 1
        if (!ApplicationBase.InitializeAsFirstInstance(ApplicationInfo.ProductName) && !allowMulti)
            return 1;

        if (showSplashScreen) {
            var splashScreen = new SplashScreen("resources/images/splashscreen.png");
            splashScreen.Show(true, false);
        }

        _ = new App();

        return 0;
    }
}

이 접근법의 장점은 응용 프로그램 자체가 인스턴스화되기 전뿐만 아니라 시작 화면이 표시되기도 전에 실행 권한을 넘겨준다는 것입니다.즉, 가능한 한 빨리 밖으로 뿜어져 나오는 것입니다.

주의: 멀티 서포트가 필요 없는 경우는, 인수의 체크와 테스트를 삭제할 수 있습니다.이것은 설명을 위해 추가된 것입니다.

단, GetProcessesByName()입니다.길이 방법은 유효합니다.Mutex는 컴퓨터 전체이며 C# 잠금이 필요합니다.WPF는 어셈블리의 GUID를 자동으로 생성하지 않기 때문에WinForms와 마찬가지로 앱 자체에서 고유 식별자를 생성해야 합니다.또한 Mutex는 전체 응용 프로그램 수명 주기 동안 계속 표시되어야 합니다. 그렇지 않으면 폐기 시 자동으로 해제됩니다.뮤텍스를 호출합니다.WaitOne() 메서드를 사용하여 잠금 및 Mutex를 실행합니다.잠금을 해제하려면 ReleaseMutex()를 선택합니다.레퍼런스:Mutex, Threading in C# by Joe Albahari... www.albahari.com/threading/

private Mutex mutex = new Mutex(false, <Author> + <AppName>);
private void Application_Startup(object sender, StartupEventArgs e)
{
    if (!mutex.WaitOne()) { App.Current.Shutdown(<ExitCode>); }
    else                  { new MainWindow(e.Args); }
}

가장 좋은 것은 프로세스 이름과 일치합니다.

App.xaml의 인터랙션 로직

`

    [DllImport("user32.dll")]
    public static extern bool ShowWindowAsync(HandleRef hWnd, int nCmdShow);
    [DllImport("user32.dll")]
    public static extern bool SetForegroundWindow(IntPtr WindowHandle);
    public const int SW_RESTORE = 9;
    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)
        {                
            Process process = Process.GetProcessesByName(proc.ProcessName).FirstOrDefault();
            IntPtr hWnd = IntPtr.Zero;
            hWnd = process.MainWindowHandle;
            ShowWindowAsync(new HandleRef(null, hWnd), SW_RESTORE);
            SetForegroundWindow(process.MainWindowHandle);
            App.Current.Shutdown();
        }
    }

`

언급URL : https://stackoverflow.com/questions/14506406/wpf-single-instance-best-practices

반응형