단일 인스턴스 WPF 애플리케이션을 작성하는 올바른 방법은 무엇입니까?


656

Windows Forms 또는 콘솔이 아닌 .NET에서 C # 및 WPF 를 사용하여 단일 인스턴스로만 실행할 수있는 응용 프로그램을 만드는 올바른 방법은 무엇입니까?

나는 그것이 뮤텍스라는 신화적인 것과 관련이 있다는 것을 알고 있습니다. 드물게 중지하고 이들 중 하나가 무엇인지 설명하는 사람을 찾을 수는 없습니다.

또한이 코드는 이미 실행중인 인스턴스에 사용자가 두 번째 인스턴스를 시작하려했음을 알리고 존재하는 경우 명령 줄 인수를 전달해야합니다.


14
응용 프로그램이 종료 될 때 CLR이 릴리스되지 않은 뮤텍스를 자동으로 해제하지 않습니까?
Cocowalla

1
@Cocowalla : 뮤텍스가 관리되는 앱에 의해 생성되었거나 기존 뮤텍스에 연결되어 있는지 알 수없는 경우 종료자는 관리되지 않는 뮤텍스를 처리해야합니다.
Ignacio Soler Garcia 9:30에

앱 인스턴스를 하나만 갖는 것이 합리적입니다. 그러나 기존 앱에 인수를 전달하면 약간 어리석은 것처럼 보입니다. 그렇게 할 이유가 없습니다. 앱을 파일 확장자와 연결하는 경우 사용자가 문서를 열고 자하는만큼의 앱을 열어야합니다. 이것이 모든 사용자가 기대하는 표준 행동입니다.
Eric Ouellet

9
@Cocowalla CLR은 기본 리소스를 관리하지 않습니다. 그러나 프로세스가 종료되면 시스템 (CLR이 아닌 OS)이 모든 핸들을 해제합니다.
IInspectable

1
@huseyint의 답변을 선호합니다. Microsoft 자체의 'SingleInstance.cs'클래스를 사용하므로 Mutexes 및 IntPtr에 대해 걱정할 필요가 없습니다. 또한 VisualBasic (yuk)에 대한 종속성이 없습니다. 자세한 내용은 codereview.stackexchange.com/questions/20871/… 를 참조하십시오 .
Heliac

답변:


537

다음은 Mutex 솔루션에 관한 매우 유용한 기사 입니다. 이 기사에서 설명하는 접근 방식은 두 가지 이유로 유리합니다.

먼저 Microsoft.VisualBasic 어셈블리에 대한 종속성이 필요하지 않습니다. 내 프로젝트가 이미 해당 어셈블리에 종속되어 있다면 다른 답변에 표시된 접근법을 사용하여 옹호 할 것입니다 . 그러나 그대로 Microsoft.VisualBasic 어셈블리를 사용하지 않고 프로젝트에 불필요한 종속성을 추가하지 않습니다.

둘째,이 기사에서는 사용자가 다른 인스턴스를 시작하려고 할 때 응용 프로그램의 기존 인스턴스를 포 그라운드로 가져 오는 방법을 보여줍니다. 그것은 여기에 설명 된 다른 Mutex 솔루션이 다루지 않는 매우 좋은 터치입니다.


최신 정보

2014 년 8 월 1 일 현재, 위에 링크 된 기사는 여전히 활성화되어 있지만 블로그는 한동안 업데이트되지 않았습니다. 그것은 결국 그것이 사라지고 옹호 된 해결책이 될지 걱정합니다. 나는 후손을 위해 기사의 내용을 재현하고 있습니다. 단어는 Sanity Free Coding 의 블로그 소유자에게만 속합니다 .

오늘 나는 내 응용 프로그램이 여러 인스턴스를 실행하지 못하게하는 코드를 리팩토링하고 싶었습니다.

이전에는 System.Diagnostics.Process 를 사용 하여 프로세스 목록에서 myapp.exe의 인스턴스를 검색했습니다. 이것이 작동하는 동안 많은 오버 헤드가 발생하고 더 깨끗한 것을 원했습니다.

나는 이것을 위해 뮤텍스를 사용할 수 있다는 것을 알았지 만 (이전에 한 번도 해본 적이 없다) 나는 코드를 줄이고 내 인생을 단순화하기 시작했다.

내 응용 프로그램 main 클래스에서 Mutex 라는 정적을 만들었습니다 .

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

명명 된 뮤텍스를 사용하면 여러 스레드와 프로세스에서 동기화를 스택 할 수 있습니다. 이것은 내가 찾고있는 마술 일뿐입니다.

Mutex.WaitOne 에는 대기 시간을 지정하는 과부하가 있습니다. 실제로 코드를 동기화하고 싶지 않기 때문에 (현재 사용 중인지 확인하십시오) Mutex.WaitOne (Timespan timeout, bool exitContext) 매개 변수와 함께 오버로드를 사용합니다 . 기다릴 수 있으면 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;
    }
}

5
이 답변은 더 적은 코드와 더 적은 라이브러리를 사용하고 최고의 기능 향상을 제공한다는 점을 바탕으로 이것을 새로운 허용 답변으로 만들 것입니다. API를 사용하여 양식을 맨 위로 가져 오는 더 올바른 방법을 알고 있다면 자유롭게 추가하십시오.
Nidonocu

11
@BlueRaja, 첫 번째 앱 인스턴스를 시작합니다. 두 번째 앱 인스턴스를 시작하면 다른 인스턴스가 이미 실행 중임을 감지하고 종료를 준비합니다. 그렇게하기 전에 첫 번째 인스턴스에 "SHOWME"기본 메시지를 보내면 첫 번째 인스턴스가 맨 위에 오게됩니다. .NET의 이벤트는 프로세스 간 통신을 허용하지 않으므로 기본 메시지가 사용됩니다.
매트 데이비스

7
다른 인스턴스에서 명령 줄을 전달하는 방법이 있습니까?
gyurisc

22
@Nam, Mutex생성자에는 단순히 문자열이 필요하므로 "This Is My Mutex"와 같이 원하는 문자열 이름을 제공 할 수 있습니다. 'Mutex'는 다른 프로세스에서 사용할 수있는 시스템 객체이므로 일반적으로 이름이 고유하여 동일한 시스템에서 다른 'Mutex'이름과 충돌하지 않도록해야합니다. 이 기사에서 암호처럼 보이는 문자열은 'Guid'입니다. 을 호출하여 프로그래밍 방식으로이를 생성 할 수 있습니다 System.Guid.NewGuid(). 이 기사의 경우 사용자는 다음과 같이 Visual Studio를 통해이를 생성했을 것입니다. msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis

6
mutex 접근 방식은 동일한 사용자가 응용 프로그램을 다시 시작하려고한다고 가정합니까? "애플리케이션의 기존 인스턴스를 포 그라운드로 가져 오는 것"은 "사용자 전환"이후에 의미가 없습니다.
dumbledad

107

Mutex 클래스를 사용할 수는 있지만 인수와 그 자체를 전달하기 위해 코드를 구현해야한다는 것을 곧 알게 될 것입니다. 글쎄, Chris Sell의 책을 읽을 때 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);
    }
}

글쎄, 나는 당신이 모든 것을 따를 수 있고이 구현을 사용하고 그것을 자신의 것으로 만들 수 있기를 바랍니다.


9
뮤텍스 솔루션은 양식과 관련이 없으므로 뮤텍스 솔루션을 고수합니다.
Steven Sudit

1
다른 접근 방식에 문제가 있기 때문에 이것을 사용했지만 후드에서 원격 기능을 사용한다고 확신합니다. 내 앱에는 두 가지 관련 문제가 있습니다. 일부 고객은 그렇지 않다고 말했지만 집에 전화하려고한다고 말합니다. 보다 자세히 살펴보면 localhost에 대한 연결입니다. 아직도, 그들은 처음에 그것을 모른다. 또한 다른 용도로 원격 기능을 사용할 수 없습니다 (제 생각에?). 이미 이미 사용되고 있습니다. 뮤텍스 접근 방식을 시도했을 때 원격을 다시 사용할 수있었습니다.
Richard Watson

4
나를 용서하십시오. 그러나 누락 된 것이 없다면 3 줄의 코드를 작성하지 말고 대신 꽤 무거운 코드를 작성하기 위해 프레임 워크를 재사용하십시오. 저축은 어디에 있습니까?
greenoldman

2
winforms에서 가능합니까?
Jack

1
응용 프로그램 인스턴스에서 InitializeComponent ()를 호출하지 않으면 리소스를 확인할 수 없습니다 ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick

84

에서 여기 .

크로스 프로세스 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를 자동으로 해제한다는 것입니다.


5
나는 WinForms에 의존하지 않는다는 사실 때문에이 답변을 허용되는 것보다 훨씬 더 좋아합니다. 개인적으로 대부분의 개발은 WPF로 옮겨 왔으며 WinForm 라이브러리를 가져 와서 그런 것을 원하지 않습니다.
Switters

5
물론, 전체 답을 할, 당신은 또한 : 다른 인스턴스에 인수를 전달 설명해야
사이먼 버컨

@Jason, 감사합니다! 그러나 나는 타임 아웃을 통과하지 않는 것을 선호합니다. 그것은 매우 주관적이고 많은 변수에 달려 있습니다. 다른 앱을 시작하려면 뮤텍스를 더 빨리 해제하십시오. 예를 들어 사용자가 가까이 확인하자마자
Eric Ouellet

@EricOuellet : Photoshop, Sublime Text, Chrome ... 등 탭이있는 모든 프로그램이이 작업을 수행합니다. "마스터"프로세스를 수행해야하는 적절한 이유가있는 경우 (설정에 대한 프로 시저 DB가 있음) 새로운 프로세스 인 것처럼 UI를 표시하고 싶습니다.
Simon Buchan

@ 시몬, 네 말이 맞아. MDI vs SDI (Multi documentinterface vs Single document interface)는 아주 오래된 것입니다. 탭에 대해 이야기 할 때는 MDI를 참조하십시오. 1998 년에 Microsoft 책은 모든 MDI 앱을 제거 할 것을 제안합니다. Microsoft는 Word, Excel ...을 SDI로 전환했습니다. Chrome 및 기타 (현재 IE)는 MDI로 다시 돌아 가기를 원합니다. 나는 개인적으로 (아무것도 / 개인적인 감정에 근거하여) 파일 연관이 선택되었을 때 새 앱을 여는 것이 더 낫다는 것을 개인적으로. 그러나 나는 지금 질문이 더 잘 이해합니다. 감사 !
Eric Ouellet

58

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과 WPF 응용 프로그램 응용 프로그램 유형 사이의 shim으로 사용해야합니다. shim은 응용 프로그램이 처음 시작될 때와 후속 실행이 시도 될 때를 감지하고 WPF 응용 프로그램 유형을 제어하여 시작 처리 방법을 결정합니다.

  • C # 사람들은 숨을 크게 쉬고 'VisualBasic DLL을 포함하고 싶지 않습니다'라는 전체를 잊어 버립니다. 이것Scott Hanselman의 말 때문에 이 꽤 많은 문제에 깨끗한 솔루션 및 더 많은 프레임 워크에 대한 당신보다 많이 알고 사람들에 의해 설계되어 있다는 사실.
  • 유용성 관점에서 볼 때 사용자가 응용 프로그램을로드하고 있고 이미 열려 있고 오류 메시지가 표시 'Another instance of the app is running. Bye'되면 매우 행복한 사용자가되지 않을 것입니다. GUI 응용 프로그램에서 해당 응용 프로그램으로 전환하고 제공된 인수를 전달해야합니다. 또는 명령 줄 매개 변수에 의미가없는 경우 최소화 된 응용 프로그램을 팝업해야합니다.

프레임 워크는 이미 이것을 지원합니다. 일부 바보는 DLL이라는 이름을 가지고 있으며 Microsoft.VisualBasic삽입되지 않았습니다.Microsoft.ApplicationUtils 그와 같은 것에 . 그것을 극복하십시오-또는 Reflector를여십시오.

팁 :이 방법을 그대로 사용하고 이미 리소스 등이있는 App.xaml을 가지고 있다면 이것도 살펴보고 싶을 것 입니다.


'이것도 살펴보기'링크를 포함시켜 주셔서 감사합니다. 바로 내가 필요한 것입니다. 그건 그렇고, 링크의 솔루션 # 3이 가장 좋습니다.
Eternal21

또한 가능한 경우 프레임 워크와 특별히 설계된 라이브러리에 위임하는 것을지지합니다.
Eniola

23

이 코드는 기본 방법으로 이동해야합니다. 봐 여기 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(...);
    }
}

참고 : 위의 방법은 프로세스 / 응용 프로그램에 고유 한 이름이 있다고 가정합니다. 프로세스 이름을 사용하여 기존 프로세서가 있는지 찾습니다. 따라서 응용 프로그램의 이름이 매우 일반적인 경우 (예 : 메모장) 위의 방법은 작동하지 않습니다.


1
또한 같은 이름의 컴퓨터에서 다른 프로그램이 실행되고 있으면 작동하지 않습니다. ProcessName실행 파일 이름에서을 뺀 값을 반환합니다 exe. "메모장"이라는 응용 프로그램을 만들고 Windows 메모장이 실행중인 경우 응용 프로그램이 실행중인 것으로 감지됩니다.
Jcl

1
이 답변에 감사드립니다. 나는 비슷한 질문을 많이 발견했으며 그 답은 항상 정교하고 혼란 스럽습니다. 이 방법 (방법 # 1)은 간단하고 명확하며 무엇보다도 실제로 코드를 실행하는 데 도움이되었습니다.
ElDoRado1239

20

글쎄, 대부분의 사용 사례에서 쉽게 작동하는 일회용 클래스가 있습니다.

다음과 같이 사용하십시오.

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
}

1
이것은 일하기 매우 쉬웠습니다. Application.Exit ();을 변경할 때까지 두 번째 응용 프로그램을 닫지 않습니다. 간단한 반환으로; 그러나 그것 이외의 위대한 것. 인정하지만 인터페이스를 사용하기 때문에 이전 솔루션을 자세히 살펴볼 것입니다. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000

15

Mutex 및 IPC를 사용하고 실행중인 인스턴스에 명령 행 인수를 전달하는 새로운 것은 WPF 단일 인스턴스 애플리케이션 입니다.


나는 이것을 큰 성공으로 사용합니다. 이와 함께 NamedPipes를 통합하면 명령 줄 인수를 원래 응용 프로그램에 전달할 수도 있습니다. 'SingleInstance.cs'클래스는 Microsoft에서 작성했습니다. CodeProject에 대한 더 읽기 쉬운 Arik Poznanski의 블로그 링크를 추가했습니다.
Heliac

링크가 끊어졌습니다.
Mike Lowery

11

표시된 답변에 대한 참조 인 코드 C # .NET 단일 인스턴스 응용 프로그램 은 훌륭한 시작입니다.

그러나 이미 존재하는 인스턴스에 모달 대화 상자가 열려 있거나 해당 대화 상자가 정보 상자와 같은 다른 양식과 같은 모달 대화 상자가 열려 있거나 관리되지 않는 대화 상자가 아닌 경우 표준 .NET 클래스를 사용하는 경우에도 OpenFileDialog). 원래 코드를 사용하면 기본 형식이 활성화되지만 모달 형식은 비활성 상태로 유지되며 이상하게 보이며 사용자는 앱을 계속 사용하려면 해당 형식을 클릭해야합니다.

따라서 Winforms 및 WPF 응용 프로그램 에서이 모든 것을 자동으로 처리하기 위해 SingleInstance 유틸리티 클래스를 만들었습니다.

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) 다음과 같이 앱 페이지를 수정하십시오 (메인 메소드를 재정의 할 수 있도록 빌드 조치를 페이지로 설정하십시오).

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

10

다음은 애플리케이션의 단일 인스턴스를 가질 수있는 예제입니다. 새 인스턴스가로드되면 인수가 실행중인 기본 인스턴스로 전달됩니다.

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

이것은 내가해야 할 일의 정말 좋은 예입니다. Nathan, 모든 인수는이 방법으로 전송됩니까? 내 앱에 7 정도가 있으며이 코드가 작동 한다고 생각 합니다.
kevp

1
이 예에서는 첫 번째 인수 만 전송되지만 모든 인수가 전송되도록 변경할 수 있습니다.
Nathan Moinvaziri 2016 년

8

일부 생각 : 응용 프로그램의 한 인스턴스 만 "불충분 한"것이 아니라고 요구하는 경우가 있습니다. 단일 사용자에 대한 여러 앱 인스턴스가 데이터베이스에 액세스 할 수 있도록 허용하는 경우 데이터베이스 앱 등이 훨씬 어려워집니다 (알다시피, 사용자의 여러 앱 인스턴스에서 열려있는 모든 레코드를 업데이트하는 모든 것) 기계 등). 먼저, "이름 충돌"의 경우, 사람이 읽을 수있는 이름을 사용하지 마십시오. GUID를 사용하거나 GUID + 사람이 읽을 수있는 이름을 사용하십시오. 누군가 지적했듯이 DOS 공격은 짜증이 나지만 악의적 인 사람이 뮤텍스 이름을 얻어 앱에 통합하는 데 어려움을 겪고 있다면, 당신은 어쨌든 거의 목표이며 뮤텍스 이름을 피들 링하는 것보다 자신을 보호하기 위해 훨씬 더 많은 일을해야합니다. 또한 new Mutex (true, "일부 GUID와 Name", AIsFirstInstance)의 변형을 사용하는 경우 Mutex가 첫 번째 인스턴스인지 여부에 대한 표시기가 이미 있습니다.


6

그렇게 단순한 질문에 대한 많은 답변. 여기에서 조금만 흔들면이 문제에 대한 나의 해결책입니다.

JIT-er는 코드의 작은 부분에 대해서만 Mutex를 사용하는 것을보고 가비지 수집 준비가 된 것으로 표시하기 때문에 Mutex를 만드는 것은 번거로울 수 있습니다. 그 Mutex를 오랫동안 사용하지 않을 것이라고 생각하는 것이 현명합니다. 실제로 응용 프로그램이 실행되는 동안이 Mutex에 매달 리려고합니다. 가비지 수집기에게 Mutex를 내버려 두도록하는 가장 좋은 방법은 다른 세대의 차고 수집을 통해 가비지 컬렉터가 살아남도록하는 것입니다. 예:

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

이 페이지에서 아이디어를 들었습니다 : http://www.ai.uga.edu/~mc/SingleInstance.html


3
응용 프로그램 클래스에 공유 사본을 저장하는 것이 쉽지 않습니까?
rossisdead


6

다음 코드는 단일 인스턴스 응용 프로그램을 등록하는 WCF 명명 된 파이프 솔루션입니다. 다른 인스턴스가 시작하려고 할 때 이벤트를 발생시키고 다른 인스턴스의 명령 줄을 수신하기 때문에 좋습니다.

System.Windows.StartupEventHandler클래스를 사용하기 때문에 WPF 에 적합하지만 쉽게 수정할 수 있습니다.

이 코드에는 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);
      }
    }
  }
}

5

단일 인스턴스 애플리케이션을 구현하기 위해 또는 최소한 프로덕션 코드에는 사용하지 않는 명명 된 mutex를 사용해서는 안됩니다. 악성 코드는 쉽게 DoS ( 서비스 거부)를 할 수 있습니다 ...


8
"명명 된 뮤텍스를 사용해서는 안됩니다"-절대 말하지 마십시오. 내 컴퓨터에서 악성 코드가 실행되고 있다면 이미 문제가 발생한 것 같습니다.
Joe

실제로 악성 코드 일 필요는 없습니다. 우연한 이름 충돌 일 수 있습니다.
Matt Davison

그럼 어떻게해야합니까?
Kevin Berridge

더 좋은 질문은 그 행동을 원하는 가능한 이유입니다. 앱을 단일 인스턴스 애플리케이션으로 설계하지 마십시오 (=). 나는 그것이 절름발이의 대답이라는 것을 알고 있지만 디자인 관점에서는 거의 항상 정답입니다. 앱에 대해 더 많이 알지 못하면 훨씬 더 말하기가 어렵습니다.
Matt Davison

2
최소한 Windows에서는 Mutexes에 액세스 제어 기능이 있으므로 하나는 객체를 가지고 놀 수 있습니다. 충돌 자체의 이름은 UUID / GUID가 개발 된 이유입니다.
NuSkooler

5

다음 코드를보십시오. 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();
    }
}

4

여기 내가 사용하는 것이 있습니다. "액티브 클릭 커"로부터 보호하기 위해 스위칭과 뮤텍스를 수행하기 위해 프로세스 열거를 결합했습니다.

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

4

Dale Ragan과 비슷한 간단한 솔루션을 찾았지만 약간 수정되었습니다. 표준 Microsoft WindowsFormsApplicationBase 클래스를 기반으로 실제로 필요한 모든 것을 수행합니다.

먼저 Windows 인스턴스를 사용하는 다른 모든 단일 인스턴스 응용 프로그램에서 사용할 수있는 SingleInstanceController 클래스를 만듭니다.

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의 두 번째 매개 변수는 널일 수 있습니다. 주어진 예에서, 창이 최대화됩니다.


4

2017-01-25를 업데이트하십시오. 몇 가지를 시도한 후 VisualBasic.dll로 이동하기로 결정하고 더 쉬워졌습니다 (적어도 나를 위해). 이전 답변을 참조로 보냈습니다 ...

참조로, 이것은 인수를 전달하지 않고 수행 한 방법입니다 (이유를 찾을 이유가 없습니다 ... 한 인스턴스에서 다른 인스턴스로 전달되는 인수가있는 단일 앱을 의미합니다). 파일 연결이 필요한 경우 각 문서에 대해 사용자 별 표준 기대에 따라 앱을 설치해야합니다. 기존 앱에 인수를 전달 해야하는 경우 vb dll을 사용했다고 생각합니다.

args (단일 인스턴스 앱)를 전달하지 않기 때문에 새 Window 메시지를 등록하지 않고 Matt Davis 솔루션에 정의 된 메시지 루프를 무시하지 않습니다. VisualBasic dll을 추가하는 것은별로 중요하지 않지만 단일 인스턴스 앱을 수행하기 위해 새 참조를 추가하지 않는 것이 좋습니다. 또한 가능한 빨리 종료하기 위해 App.Startup 재정의에서 Shutdown을 호출하는 대신 Main을 사용하여 새 클래스를 설치하는 것이 좋습니다.

누군가가 그것을 좋아하기를 바랍니다 ... 또는 조금 영감을 줄 것입니다 :-)

프로젝트 시작 클래스는 '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);

3

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

-statement에 추가 MessageBox.Show하고 if"Application already running"을 넣을 수 있습니다 .
이것은 누군가에게 도움이 될 수 있습니다.


4
두 개의 프로세스가 동시에 시작되면 두 개의 활성 프로세스가 표시되고 자체 종료 될 수 있습니다.
AT

@AT 예, 관리자로 실행되는 앱에도 유용 할 수 있습니다.
newbieguy

응용 프로그램의 복사본을 만들어 이름을 바꾸면 원본과 복사본을 동시에 실행할 수 있습니다.
Dominique Bijnens

2

명명 된 뮤텍스가 모노에서 전역 적이 지 않기 때문에 명명 된 뮤텍스 기반 접근 방식은 크로스 플랫폼이 아닙니다. 프로세스 열거 기반 접근 방식에는 동기화가 없으며 잘못된 동작이 발생할 수 있습니다 (예 : 동시에 시작된 여러 프로세스가 타이밍에 따라 모두 자체 종료 될 수 있음). 콘솔 응용 프로그램에서는 윈도우 시스템 기반 접근 방식이 바람직하지 않습니다. 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.");
            }
        }
    }
}

2

여러 인스턴스를 방지하기 위해 솔루션에서 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();
}

1

뮤텍스 솔루션 사용 :

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

1

다음은 응용 프로그램이 사용자 정의 창 메시지 또는 맹목적으로 프로세스 이름을 검색하지 않고 기존 창을 전경으로 가져올 수있게 해주는 가벼운 솔루션입니다.

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

편집 : 뮤텍스를 저장하고 초기화하고 New를 정적으로 만들 수도 있지만 일단 뮤텍스를 완료하면 명시 적으로 처분 / 해제해야합니다. 개인적으로, 응용 프로그램이 Main의 끝에 도달하지 않고 닫히더라도 자동으로 처리되므로 뮤텍스를 로컬로 유지하는 것이 좋습니다.



1

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

1

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

1

[아래에 콘솔 및 wpf 응용 프로그램 용 샘플 코드를 제공했습니다.]

당신은 단지의 가치를 확인해야 createdNew명명 된 Mutex 인스턴스를 생성 한 후에 만 ​​변수 (아래 예!) 만 확인하면됩니다.

부울 createdNewfalse 를 반환합니다 .

"YourApplicationNameHere"라는 Mutex 인스턴스가 시스템 어딘가에 이미 생성 된 경우

부울 createdNewtrue 를 반환 합니다.

이것이 시스템에서 "YourApplicationNameHere"라는 첫 번째 Mutex 인 경우


콘솔 응용 프로그램-예 :

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

1

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

1

세마포어를 사용하여 기존 인스턴스가 이미 실행 중인지 확인하고 WPF 애플리케이션에서 작동하며 TcpListener 및 TcpClient를 사용하여 두 번째 인스턴스에서 이미 실행중인 첫 번째 인스턴스로 인수를 전달할 수 있는 제안 된 솔루션을 여기 에서 확인 하십시오 .

.NET Framework뿐만 아니라 .NET Core에서도 작동합니다.


1

여기서 짧은 해결책을 찾을 수 없으므로 누군가가 이것을 좋아하기를 바랍니다.

2018-09-20에 업데이트 됨

이 코드를 당신의 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());
}

이것은 경쟁 조건을 소개합니다. 뮤텍스를 사용해야합니다.
georgiosd

1
두 인스턴스를 동시에 가동하면 이것이 작동한다는 보장은 없습니다. 두 개의 다른 스레드에서 변수를 업데이트하는 것과 같습니다. 까다로운 위험한 사업. 힘을 사용, 루크 :)
georgiosd

@georgiosd 아 무슨 말인지 알 겠어. 누군가가 .exe를 시작하고 이름을 변경하는 것처럼. 예, 이것은 더 많은 시간을 시작하는 방법이지만, 이름이 변경되면 정상적으로 .exe가 작동하지 않습니다. 답변을 업데이트하겠습니다 ^^ 감사합니다. Luke : D 지적 해주셔서 감사합니다. :)
Deniz

1
@Deniz만이 아닙니다. 두 개의 프로세스를 실제로 빠르게 시작하면 프로세스 목록이나 프로세스를 가져 오는 메소드가 여전히 하나만 나타나는 동안 실행될 수 있습니다. 이것은 당신과 관련이없는
가장 중요한 사건

@georgiosd 증명할 수 있습니까? IV는 당신을 위해 그것을 테스트했기 때문에 hehe. 그러나 심지어 "정말 빨리"조차 불가능했습니다! : P 그래서 나는 왜 당신이 단지 사건이 아닌이 무고한 코드를 싫어하는 것을 믿지 못합니다 : D
Deniz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.