web-dev-qa-db-ja.com

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)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 AP​​I呼び出しは_UInt32_...を返す必要がありますが、HwndSourceHookはメッセージ値として_Int32_のみを受け入れます...予期しない動作(たとえば_Int32.MaxValue_)より大きい結果ですか?

4)OnStartupオーバーライドで...別のインスタンスが既に実行されていて、アプリケーションをシャットダウンする場合でも、base.OnStartup(e);を実行する必要がありますか?

5)Topmost値を設定する必要のない既存のインスタンスを一番上に持ってくるより良い方法はありますか?たぶんActivate()

6)私のアプローチに欠陥がありますか?マルチスレッド、悪い例外処理などに関する何か?たとえば... OnStartupOnExitの間でアプリケーションがクラッシュした場合はどうなりますか?

35

1)標準的なDispose実装のように見えます。本当に必要なわけではありませんが(ポイント6を参照)、害はありません。 (閉鎖時の掃除は、家を焼く前に家を掃除するようなものですが、私見ですが、この問題に関する意見は異なります。)

とにかく、直接呼び出されなくても、クリーンアップメソッドの名前として「Dispose」を使用しないのはなぜですか。 「クリーンアップ」と呼ぶこともできますが、人間用のコードも作成することを忘れないでください。Disposeは見慣れているように見えます。だから、「廃棄」に行きます。

2)私はいつもm_Mutex = new Mutex(false, mutexName);を見てきましたが、技術的な利点というよりは慣習だと思います。

3)MSDNから:

メッセージが正常に登録された場合、戻り値は0xC000〜0xFFFFの範囲のメッセージ識別子です。

だから私は心配しません。通常、このクラスの関数では、UIntは「Intに収まらないため、UIntを使用してさらに何かをしましょう」には使用されませんが、「関数は負の値を返さない」という契約を明確にします。

4)#1と同じ理由で、シャットダウンする場合は呼び出しを避けます

5)いくつかの方法があります。 Win32で最も簡単な方法は、2番目のインスタンスにSetForegroundWindowの呼び出しを実行させることです(こちらをご覧ください: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx );ただし、同等のWPF機能があるかどうか、またはそれをPInvokeする必要があるかどうかはわかりません。

6)

たとえば、OnStartupとOnExitの間でアプリケーションがクラッシュするとどうなりますか?

問題ありません。プロセスが終了すると、そのプロセスが所有するすべてのハンドルが解放されます。ミューテックスも解放されます。

要するに、私の推奨事項:

  • 名前付き同期オブジェクトに基づいたアプローチを使用します。これは、Windowsプラットフォームでより確立されています。 (ターミナルサーバーなどのマルチユーザーシステムを検討する場合は注意してください!同期オブジェクトには、おそらくユーザー名/ SIDとアプリケーション名の組み合わせとして名前を付けてください)
  • Windows APIを使用して前のインスタンス(ポイント#5のリンクを参照)、または同等のWPFを発生させます。
  • おそらく、クラッシュを心配する必要はありません(カーネルはカーネルオブジェクトのrefカウンターを減らします;とにかく少しテストを行います)、しかし、改善を提案するかもしれません:あなたの最初のアプリケーションインスタンスがクラッシュせずにハングしたら? (Firefoxで起こります。それはあなたにも起こったと確信しています!ウィンドウも、ffプロセスも、新しいウィンドウを開くことはできません)。その場合は、a)アプリケーション/ウィンドウが応答するかどうかをテストするために、1つまたは2つの別の手法を組み合わせるとよいかもしれません。 b)ハングしたインスタンスを見つけて終了する

たとえば、テクニック(ウィンドウにメッセージを送信/投稿しようとする-応答がない場合、スタックしている)とMSKテクニックを使用して、古いプロセスを見つけて終了できます。その後、正常に起動します。

7

いくつかの選択肢がありますが、

  • ミューテックス
  • プロセスマネージャー
  • 名前付きセマフォ
  • リスナーソケットを使用する

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

リスナーソケットを使用する

別のアプリケーションに信号を送る1つの方法は、そのアプリケーションへのTcp接続を開くことです。ソケットを作成し、ポートにバインドし、バックグラウンドスレッドで接続をリッスンします。これが成功した場合、正常に実行します。そうでない場合は、そのポートに接続します。これは、2番目のアプリケーションの起動が試行されたことを他のインスタンスに通知します。必要に応じて、元のインスタンスはメインウィンドウを前面に表示できます。

「セキュリティ」ソフトウェア/ファイアウォールが問題になる場合があります。

Win32と共にシングルインスタンスアプリケーションC#.Net

41
C-va

ユーザーエクスペリエンスを少し向上させたかった-別のインスタンスが既に実行されている場合は、2番目のインスタンスに関するエラーを表示するのではなく、アクティブ化しましょう。これが私の実装です。

名前付きMutexを使用して、1つのインスタンスのみが実行されていることを確認し、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のBringToForeground:

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

私のために働く:)

32
ZakiMa

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

それを処理する最も簡単な方法は、名前付きセマフォを使用することです。このようなものを試してみてください...

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

}
5
blaise

私はこれに単純なTCPソケットを使用しました(Javaでは、10年前)。

  1. 起動時に事前定義されたポートに接続し、接続が受け入れられた場合は別のインスタンスが実行され、そうでない場合はTCP Listener
  2. 誰かがあなたに接続したら、ウィンドウをポップアップして切断します
4
Luuk

2番目のインスタンスを防止するには、

  • eventWaitHandleを使用して(イベントについて話しているため)、
  • タスクを使用して、
  • mutexコードは不要、
  • tCPなし、
  • ピンボークスなし
  • garbageCollectionなどはありません。
  • スレッドセーブ

これは次のように行うことができます(これは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
        }
    }
}

2番目のバージョン:上記に加えて、ウィンドウを表示するように他のインスタンスに信号を送ります(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 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-compatible-utility-classes/tils.SingleInstance.cs

4
BananaAcid

これは簡単な解決策です。この場合、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を追加することを忘れないでください

2
Hamed

古いインスタンスをフォアグラウンドに持ってくる例を次に示します。

_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ランタイムを変更しない場合にのみ機能します。

編集:

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)を呼び出さないことを忘れないでください!

0
Dave_cz