Cách chính xác để tạo một ứng dụng WPF đơn lẻ là gì?


655

Sử dụng C # và WPF trong .NET (chứ không phải Windows Forms hoặc console), cách chính xác để tạo một ứng dụng chỉ có thể chạy như một phiên bản duy nhất là gì?

Tôi biết nó có liên quan đến một thứ thần thoại gọi là mutex, hiếm khi tôi có thể tìm ai đó làm phiền để giải thích và giải thích một trong những thứ này là gì.

Mã cũng cần thông báo cho cá thể đã chạy mà người dùng đã cố gắng bắt đầu một phiên bản thứ hai và cũng có thể vượt qua bất kỳ đối số dòng lệnh nào nếu có tồn tại.


14
CLR không tự động phát hành bất kỳ trường hợp đột biến nào chưa được phát hành khi ứng dụng kết thúc chứ?
Cocowalla

1
@Cocowalla: trình hoàn thiện sẽ loại bỏ các mutex không được quản lý trừ khi nó không thể biết liệu mutex được tạo bởi ứng dụng được quản lý hay gắn liền với một mutex hiện có.
Ignacio Soler Garcia

Chỉ có một ví dụ của ứng dụng của bạn là hợp lý. Nhưng việc truyền các đối số cho một ứng dụng đã tồn tại có vẻ hơi ngớ ngẩn. Tôi không thể thấy bất kỳ lý do để làm như vậy. Nếu bạn liên kết một ứng dụng với phần mở rộng tệp, bạn nên mở nhiều ứng dụng mà người dùng muốn mở tài liệu. Đó là hành vi tiêu chuẩn mà mọi người dùng đều mong đợi.
Eric Ouellet

9
@Cocowalla CLR không quản lý tài nguyên bản địa. Tuy nhiên, nếu một quá trình chấm dứt, tất cả các xử lý sẽ được hệ thống giải phóng (HĐH, không phải CLR).
IInspectable

1
Tôi thích câu trả lời của @huseyint. Nó sử dụng lớp 'SingleInstance.cs' của chính Microsoft, vì vậy bạn không phải lo lắng về Mutexes và IntPtrs. Ngoài ra, không phụ thuộc vào VisualBasic (yuk). Xem codereview.stackexchange.com/questions/20871/ trên để biết thêm ...
Heliac

Câu trả lời:


537

Đây là một bài viết rất hay về giải pháp Mutex. Cách tiếp cận được mô tả bởi bài viết là thuận lợi vì hai lý do.

Đầu tiên, nó không yêu cầu sự phụ thuộc vào cụm Microsoft.VisualBasic. Nếu dự án của tôi đã có sự phụ thuộc vào hội đồng đó, có lẽ tôi sẽ ủng hộ việc sử dụng phương pháp tiếp cận trong câu trả lời khác . Nhưng vì nó là như vậy, tôi không sử dụng hội đồng Microsoft.VisualBasic và tôi không muốn thêm một sự phụ thuộc không cần thiết vào dự án của mình.

Thứ hai, bài viết cho thấy làm thế nào để đưa phiên bản hiện có của ứng dụng lên nền trước khi người dùng cố gắng bắt đầu một thể hiện khác. Đó là một liên lạc rất hay mà các giải pháp Mutex khác được mô tả ở đây không giải quyết được.


CẬP NHẬT

Kể từ ngày 8/1/2014, bài viết tôi liên kết ở trên vẫn còn hoạt động, nhưng blog đã không được cập nhật trong một thời gian. Điều đó khiến tôi lo lắng rằng cuối cùng nó có thể biến mất, và với nó, giải pháp được ủng hộ. Tôi đang sao chép nội dung của bài viết ở đây cho hậu thế. Các từ chỉ thuộc về chủ sở hữu blog tại Sanity Free Coding .

Hôm nay tôi muốn cấu trúc lại một số mã cấm ứng dụng của tôi chạy nhiều phiên bản của chính nó.

Trước đây tôi đã sử dụng System.Diagnostics.Process để tìm kiếm một phiên bản myapp.exe của tôi trong danh sách quy trình. Trong khi điều này hoạt động, nó mang lại rất nhiều chi phí, và tôi muốn một cái gì đó sạch hơn.

Biết rằng tôi có thể sử dụng một mutex cho việc này (nhưng chưa bao giờ thực hiện nó trước đây), tôi bắt đầu cắt giảm mã của mình và đơn giản hóa cuộc sống của tôi.

Trong lớp ứng dụng chính của tôi, tôi đã tạo một tĩnh có tên Mutex :

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

Có một mutex được đặt tên cho phép chúng ta xếp chồng đồng bộ hóa qua nhiều luồng và quy trình, đó là điều kỳ diệu mà tôi đang tìm kiếm.

Mutex.WaitOne có tình trạng quá tải chỉ định một lượng thời gian để chúng tôi chờ đợi. Vì chúng tôi không thực sự muốn đồng bộ hóa mã của mình (chỉ cần kiểm tra xem nó có đang được sử dụng không), chúng tôi sử dụng quá tải với hai tham số: Mutex.WaitOne (Hết thời gian, bool exitContext) . Đợi một cái trả về true nếu nó có thể nhập và false nếu không. Trong trường hợp này, chúng tôi không muốn chờ đợi gì cả; Nếu mutex của chúng tôi đang được sử dụng, hãy bỏ qua nó và tiếp tục, vì vậy chúng tôi chuyển qua TimeSpan.Zero (đợi 0 mili giây) và đặt exitContext thành true để chúng tôi có thể thoát khỏi bối cảnh đồng bộ hóa trước khi chúng tôi cố gắng khóa nó. Sử dụng cái này, chúng tôi bọc mã Application.Run của mình bên trong một cái gì đó như thế này:

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

Vì vậy, nếu ứng dụng của chúng tôi đang chạy, WaitOne sẽ trả về false và chúng tôi sẽ nhận được một hộp thông báo.

Thay vì hiển thị hộp thông báo, tôi đã chọn sử dụng một Win32 nhỏ để thông báo cho cá thể đang chạy của mình rằng ai đó đã quên rằng nó đang chạy (bằng cách đưa nó lên trên cùng của tất cả các cửa sổ khác). Để đạt được điều này, tôi đã sử dụng PostMessage để phát một thông điệp tùy chỉnh đến mọi cửa sổ (tin nhắn tùy chỉnh đã được đăng ký với RegisterWindowMessage bởi ứng dụng đang chạy của tôi, nghĩa là chỉ ứng dụng của tôi biết nó là gì) thì phiên bản thứ hai của tôi thoát ra. Ví dụ ứng dụng đang chạy sẽ nhận được thông báo đó và xử lý nó. Để làm điều đó, tôi đã vượt qua WndProc ở dạng chính của mình và lắng nghe thông báo tùy chỉnh của mình. Khi tôi nhận được thông báo đó, tôi đặt thuộc tính TopMost của biểu mẫu thành true để đưa nó lên trên cùng.

Đây là những gì tôi đã kết thúc với:

  • Chương trình.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 (một phần phía trước)
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
Trên cơ sở câu trả lời này sử dụng ít mã hơn và ít thư viện hơn và cung cấp chức năng nâng cao lên hàng đầu, tôi sẽ biến câu trả lời mới được chấp nhận này. Nếu bất cứ ai biết một cách chính xác hơn để đưa biểu mẫu lên hàng đầu bằng API, hãy thoải mái thêm nó.
Nidonocu

11
@BlueRaja, bạn khởi động ứng dụng đầu tiên. Khi bạn khởi động phiên bản ứng dụng thứ hai, nó phát hiện ra một phiên bản khác đang chạy và chuẩn bị tắt máy. Trước khi làm như vậy, nó sẽ gửi một thông điệp gốc "SHOWME" đến phiên bản đầu tiên, đưa phiên bản đầu tiên lên đầu. Các sự kiện trong .NET không cho phép giao tiếp giữa các quá trình, đó là lý do tại sao thông điệp gốc được sử dụng.
Matt Davis

7
Có cách nào để vượt qua các dòng lệnh từ ví dụ khác không?
gyurisc

22
@ Nam, hàm Mutextạo chỉ cần một chuỗi, vì vậy bạn có thể cung cấp bất kỳ tên chuỗi nào bạn muốn, ví dụ: "Đây là Mutex của tôi". Vì 'Mutex' là một đối tượng hệ thống có sẵn cho các quy trình khác, nên bạn thường muốn tên này là duy nhất để nó không bị xung đột với các tên 'Mutex' khác trên cùng hệ thống. Trong bài viết, chuỗi tìm kiếm khó hiểu là một 'Hướng dẫn'. Bạn có thể tạo chương trình này bằng cách gọi System.Guid.NewGuid(). Trong trường hợp của bài viết, người dùng có thể đã tạo nó thông qua Visual Studio như được hiển thị ở đây: msdn.microsoft.com/en-us/l Library / ms241442 (VS.80) .aspx
Matt Davis

6
Có phải cách tiếp cận mutex cho rằng cùng một người dùng đang cố gắng khởi động lại ứng dụng? Chắc chắn việc đưa "phiên bản hiện có của ứng dụng lên nền trước" không có ý nghĩa gì sau khi 'người dùng chuyển đổi'
dumbledad

107

Bạn có thể sử dụng lớp Mutex, nhưng bạn sẽ sớm phát hiện ra rằng bạn sẽ cần triển khai mã để truyền các đối số và chính bạn. Chà, tôi đã học được một mẹo khi lập trình trong WinForms khi tôi đọc cuốn sách của Chris Sell . Thủ thuật này sử dụng logic đã có sẵn cho chúng tôi trong khung. Tôi không biết về bạn, nhưng khi tôi tìm hiểu về những thứ tôi có thể sử dụng lại trong khung, đó thường là con đường tôi đi thay vì phát minh lại bánh xe. Tất nhiên trừ khi nó không làm mọi thứ tôi muốn.

Khi tôi vào WPF, tôi đã nghĩ ra cách sử dụng cùng mã đó, nhưng trong một ứng dụng WPF. Giải pháp này sẽ đáp ứng nhu cầu của bạn dựa trên câu hỏi của bạn.

Đầu tiên, chúng ta cần tạo lớp ứng dụng của chúng tôi. Trong lớp này, chúng ta sẽ ghi đè sự kiện OnStartup và tạo một phương thức gọi là Kích hoạt, sẽ được sử dụng sau này.

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

Thứ hai, chúng ta sẽ cần tạo một lớp có thể quản lý các thể hiện của chúng ta. Trước khi chúng ta trải qua điều đó, chúng ta thực sự sẽ sử dụng lại một số mã trong tập hợp Microsoft.VisualBasic. Vì, tôi đang sử dụng C # trong ví dụ này, tôi phải tham chiếu đến hội đồng. Nếu bạn đang sử dụng VB.NET, bạn không phải làm gì cả. Lớp chúng ta sẽ sử dụng là WindowsFormsApplicationBase và kế thừa trình quản lý cá thể của chúng ta khỏi nó và sau đó tận dụng các thuộc tính và sự kiện để xử lý sự đơn điệu.

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

Về cơ bản, chúng tôi đang sử dụng các bit VB để phát hiện các trường hợp đơn lẻ và xử lý tương ứng. OnStartup sẽ được kích hoạt khi tải phiên bản đầu tiên. OnStartupNextInstance được kích hoạt khi ứng dụng được chạy lại. Như bạn có thể thấy, tôi có thể nhận được những gì đã được truyền trên dòng lệnh thông qua các đối số sự kiện. Tôi đặt giá trị cho một trường ví dụ. Bạn có thể phân tích dòng lệnh ở đây hoặc bạn có thể chuyển nó đến ứng dụng của mình thông qua hàm tạo và lệnh gọi phương thức Kích hoạt.

Thứ ba, đã đến lúc tạo EntryPoint của chúng tôi. Thay vì làm mới ứng dụng như bạn thường làm, chúng tôi sẽ tận dụng lợi thế của SingleInstanceManager.

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

Vâng, tôi hy vọng bạn có thể làm theo mọi thứ và có thể sử dụng triển khai này và biến nó thành của riêng bạn.


9
Tôi gắn bó với giải pháp mutex vì nó không liên quan gì đến các hình thức.
Steven Sudit

1
Tôi đã sử dụng điều này bởi vì tôi có vấn đề với các phương pháp khác, nhưng tôi khá chắc chắn rằng nó sử dụng điều khiển từ xa dưới mui xe. Ứng dụng của tôi đã có hai vấn đề liên quan - một số khách hàng nói rằng nó cố gắng gọi điện về nhà mặc dù họ đã nói không. Khi họ xem xét kỹ hơn, kết nối là đến localhost. Tuy nhiên, ban đầu họ không biết điều đó. Ngoài ra, tôi không thể sử dụng từ xa cho mục đích khác (tôi nghĩ vậy?) Vì nó đã được sử dụng cho việc này. Khi tôi thử phương pháp mutex, sau đó tôi có thể sử dụng từ xa một lần nữa.
Richard Watson

4
Hãy tha thứ cho tôi, nhưng trừ khi tôi thiếu một cái gì đó, bạn đã tránh viết 3 dòng mã và thay vào đó bạn sử dụng lại khung chỉ để viết mã khá nặng để làm điều đó. Vậy tiết kiệm ở đâu?
greenoldman

2
nó có thể làm điều đó trong winforms?
Jack

1
Nếu bạn không gọi InitializeComponent () trong trường hợp ứng dụng, bạn sẽ không thể giải quyết các tài nguyên ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick

84

Từ đây .

Một cách sử dụng phổ biến cho Mutex xử lý chéo là để đảm bảo rằng chỉ có một phiên bản của chương trình có thể chạy tại một thời điểm. Đây là cách nó được thực hiện:

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

Một tính năng hay của Mutex là nếu ứng dụng chấm dứt mà không có ReleaseMutex lần đầu tiên được gọi, CLR sẽ tự động giải phóng Mutex.


5
Tôi phải nói rằng, tôi thích câu trả lời này hơn rất nhiều so với câu trả lời được chấp nhận đơn giản vì thực tế là nó không phụ thuộc vào WinForms. Cá nhân tôi, hầu hết sự phát triển của tôi đã chuyển sang WPF và tôi không muốn phải kéo vào các thư viện WinForm cho những thứ như thế này.
Switters

5
Tất nhiên, để có một câu trả lời đầy đủ, bạn cũng phải mô tả việc chuyển các đối số sang ví dụ khác :)
Simon Buchan

@Jason, tốt, cảm ơn! Nhưng tôi không thích vượt qua bất kỳ thời gian chờ. Đó là rất nhiều chủ quan và phụ thuộc vào rất nhiều biến. Nếu bạn muốn kích hoạt một ứng dụng khác để bắt đầu, chỉ cần phát hành mutex của bạn nhanh hơn .. ví dụ ngay khi người dùng xác nhận đóng
Eric Ouellet

@EricOuellet: Cứ gần như mọi chương trình có các tab thực hiện điều này - Photoshop, Sublime Text, Chrome .... Nếu bạn có lý do chính đáng để có quy trình "chính" (giả sử bạn có DB trong cài đặt), bạn có thể muốn có nó hiển thị UI như thể đó là một quá trình mới.
Simon Buchan

@Simon, bạn nói đúng. Tôi chỉ tự hỏi bản thân về một điều rất cũ ... MDI vs SDI (Giao diện tài liệu đa giao diện và giao diện tài liệu đơn). Khi bạn nói về các tab, bạn tham khảo MDI. Năm 1998, một cuốn sách của Microsoft đề nghị loại bỏ mọi ứng dụng MDI. Microsoft đã chuyển Word, Excel ... sang SDI mà tôi nghĩ nó đơn giản và tốt hơn. Tôi hiểu rằng Chrome và những người khác (hiện là IE) muốn quay lại MDI. Tôi cá nhân (dựa trên không có gì / cảm xúc cá nhân) rằng vẫn tốt hơn để mở một ứng dụng mới khi tập tin PGS được chọn. Nhưng tôi hiểu rõ hơn câu hỏi bây giờ. Cảm ơn !
Eric Ouellet

58

MSDN thực sự có một ứng dụng mẫu cho cả C # và VB để thực hiện chính xác điều này: http://msdn.microsoft.com/en-us/l Library / ms771662 ( v = VS.90) .aspx

Kỹ thuật phổ biến và đáng tin cậy nhất để phát triển phát hiện đơn lẻ là sử dụng cơ sở hạ tầng từ xa Microsoft .NET Framework (System.Remote). Microsoft .NET Framework (phiên bản 2.0) bao gồm một loại, WindowsFormsApplicationBase, đóng gói các chức năng từ xa cần thiết. Để kết hợp loại này vào một ứng dụng WPF, một loại cần phải xuất phát từ nó và được sử dụng như là một sự kết hợp giữa phương thức điểm nhập tĩnh của ứng dụng, Chính và loại Ứng dụng của ứng dụng WPF. Shim phát hiện khi một ứng dụng được khởi chạy lần đầu tiên và khi các lần khởi chạy tiếp theo được thử và năng suất sẽ kiểm soát loại Ứng dụng WPF để xác định cách xử lý các lần khởi chạy.

  • Đối với C #, mọi người chỉ cần hít một hơi thật sâu và quên đi toàn bộ 'Tôi không muốn bao gồm VisualBasic DLL'. Bởi vì điều này và những gì Scott Hanselman nói và thực tế rằng điều này khá nhiều là giải pháp sạch nhất cho vấn đề và được thiết kế bởi những người biết nhiều về khuôn khổ hơn bạn.
  • Từ quan điểm về khả năng sử dụng, thực tế là nếu người dùng của bạn đang tải một ứng dụng và nó đã mở và bạn đang gửi cho họ một thông báo lỗi như thế 'Another instance of the app is running. Bye'thì họ sẽ không phải là một người dùng rất hạnh phúc. Bạn chỉ cần PHẢI (trong một ứng dụng GUI) chuyển sang ứng dụng đó và chuyển vào các đối số được cung cấp - hoặc nếu các tham số dòng lệnh không có ý nghĩa thì bạn phải bật ứng dụng có thể đã được thu nhỏ.

Khung công tác đã hỗ trợ cho việc này - chỉ là một tên ngốc tên là DLL Microsoft.VisualBasicvà nó đã không được đưa vào Microsoft.ApplicationUtilshoặc một cái gì đó tương tự. Hãy vượt qua nó - hoặc mở Reflector.

Mẹo: Nếu bạn sử dụng phương pháp này chính xác như hiện tại và bạn đã có App.xaml với các tài nguyên, v.v. bạn cũng sẽ muốn xem xét điều này .


Cảm ơn bạn đã bao gồm liên kết 'hãy xem cái này quá'. Đó chính xác là những gì tôi cần. Nhân tiện, giải pháp số 3 trong liên kết của bạn là giải pháp tốt nhất.
Eternal21

Tôi cũng là người ủng hộ việc ủy ​​thác khuôn khổ và các thư viện được thiết kế đặc biệt nếu có thể.
Eniola

23

Mã này nên đi đến phương thức chính. Nhìn vào đây để biết thêm thông tin về phương pháp chính trong 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; 
    }
}

Cách 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(...);
    }
}

Lưu ý: Các phương thức trên giả định quy trình / ứng dụng của bạn có một tên duy nhất. Bởi vì nó sử dụng tên tiến trình để tìm nếu có bất kỳ bộ xử lý hiện có. Vì vậy, nếu ứng dụng của bạn có một tên rất phổ biến (ví dụ: Notepad), cách tiếp cận trên sẽ không hoạt động.


1
Ngoài ra, điều này sẽ không hoạt động nếu có bất kỳ chương trình nào khác chạy trên máy tính của bạn có cùng tên. ProcessNametrả về tên tệp thực thi trừ đi exe. Nếu bạn tạo một ứng dụng có tên "Notepad" và notepad của windows đang chạy, nó sẽ phát hiện ra nó là ứng dụng của bạn đang chạy.
Jcl

1
Cảm ơn câu trả lời này. Tôi đã tìm thấy rất nhiều câu hỏi tương tự và câu trả lời luôn rất phức tạp và / hoặc khó hiểu Tôi đã thấy chúng vô dụng. Cái này (Phương pháp số 1) rất đơn giản, rõ ràng và hầu hết tất cả nó thực sự giúp tôi làm cho mã của mình chạy.
ElDoRado1239

20

Chà, tôi có một Lớp dùng một lần cho cái này hoạt động dễ dàng cho hầu hết các trường hợp sử dụng:

Sử dụng nó như thế này:

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

        // Initialize program here.
    }
}

Đây là:

/// <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
cái này khá dễ để làm việc Nó sẽ không đóng ứng dụng thứ hai cho đến khi tôi thay đổi Application.Exit (); để trở lại đơn giản; nhưng khác hơn là nó tuyệt vời. Mặc dù tôi thừa nhận tôi sẽ xem xét giải pháp trước đó gần hơn vì nó sử dụng một giao diện. blog.microsoft.co.il/bloss/arik/archive/2010/05/28/ trên
hal9000

15

Một cái mới sử dụng công cụ Mutex và IPC, đồng thời chuyển bất kỳ đối số dòng lệnh nào sang phiên bản đang chạy, là Ứng dụng sơ thẩm WPF .


Tôi sử dụng điều này với thành công lớn. Nếu bạn kết hợp NamedPipes với điều này, bạn cũng có thể chuyển các đối số dòng lệnh cho ứng dụng gốc. Lớp, 'SingleInstance.cs', được viết bởi Microsoft. Tôi đã thêm một liên kết khác vào phiên bản dễ đọc hơn của blog Arik Poznanski trên CodeProject.
Heliac

11

C # .NET Ứng dụng sơ thẩm duy nhất là tham chiếu cho câu trả lời được đánh dấu là một khởi đầu tuyệt vời.

Tuy nhiên, tôi thấy nó không xử lý tốt các trường hợp khi cá thể đã tồn tại mở hộp thoại phương thức, cho dù hộp thoại đó là một hộp được quản lý (như một Mẫu khác như hộp về) hay một hộp không được quản lý (như OpenFileDialog ngay cả khi sử dụng lớp .NET tiêu chuẩn). Với mã gốc, biểu mẫu chính được kích hoạt, nhưng dạng thức vẫn không hoạt động, trông lạ, cộng với người dùng phải nhấp vào nó để tiếp tục sử dụng ứng dụng.

Vì vậy, tôi đã tạo một lớp tiện ích SingleInstance để xử lý tất cả điều này khá tự động cho các ứng dụng Winforms và WPF.

Winforms :

1) sửa đổi lớp Chương trình như thế này:

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) sửa đổi lớp cửa sổ chính như thế này:

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) sửa đổi trang Ứng dụng như thế này (và đảm bảo bạn đặt hành động xây dựng của nó thành trang để có thể xác định lại phương thức Chính):

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) sửa đổi lớp cửa sổ chính như thế này:

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

Và đây là lớp tiện ích:

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

Dưới đây là một ví dụ cho phép bạn có một phiên bản ứng dụng. Khi bất kỳ trường hợp mới nào tải, chúng chuyển các đối số của chúng sang thể hiện chính đang chạy.

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

Đây là một ví dụ thực sự tốt đẹp về những gì tôi phải làm. Nathan, có phải tất cả các đối số được gửi bằng phương pháp này? Tôi có 7 hoặc hơn trong ứng dụng của mình và tôi nghĩ rằng mã này sẽ hoạt động.
kevp

1
Trong ví dụ của tôi, chỉ có đối số đầu tiên được gửi, nhưng nó có thể được thay đổi để tất cả chúng được gửi.
Nathan Moinvaziri

8

Chỉ là một vài suy nghĩ: Có những trường hợp khi yêu cầu chỉ một trường hợp của ứng dụng không "khập khiễng" như một số bạn có thể tin. Các ứng dụng cơ sở dữ liệu, v.v ... khó khăn hơn nhiều nếu một ứng dụng cho phép nhiều phiên bản ứng dụng cho một người dùng truy cập cơ sở dữ liệu (bạn biết, tất cả các cập nhật tất cả các bản ghi được mở trong nhiều phiên bản của ứng dụng trên người dùng máy, v.v.). Đầu tiên, đối với "điều va chạm tên, không sử dụng tên có thể đọc được của con người - thay vào đó hãy sử dụng GUID hoặc, thậm chí tốt hơn là GUID + tên người có thể đọc được. Cơ hội va chạm tên chỉ rơi ra khỏi radar và Mutex không quan tâm Như ai đó đã chỉ ra, một cuộc tấn công DOS sẽ hút, nhưng nếu kẻ độc hại gặp rắc rối khi lấy tên mutex và kết hợp nó vào ứng dụng của họ, dù sao bạn cũng là một mục tiêu và sẽ phải làm NHIỀU hơn để bảo vệ bản thân mình hơn là chỉ sử dụng một tên mutex. Ngoài ra, nếu người ta sử dụng biến thể của: Mutex mới (đúng, "một số GUID cộng với tên", ngoài AIsFirstInstance), bạn đã có chỉ số của mình về việc Mutex có phải là ví dụ đầu tiên hay không.


6

Rất nhiều câu trả lời cho một câu hỏi có vẻ đơn giản như vậy. Chỉ cần lắc mọi thứ lên một chút ở đây là giải pháp của tôi cho vấn đề này.

Tạo Mutex có thể gây rắc rối vì JIT-er chỉ thấy bạn sử dụng nó cho một phần nhỏ mã của bạn và muốn đánh dấu nó là sẵn sàng để thu gom rác. Rất nhiều người muốn thông minh hơn bạn nghĩ rằng bạn sẽ không sử dụng Mutex đó lâu. Trong thực tế, bạn muốn bám vào Mutex này miễn là ứng dụng của bạn đang chạy. Cách tốt nhất để nói với người thu gom rác để lại cho bạn Mutex một mình là bảo họ giữ cho nó tồn tại mặc dù ngoài các thế hệ khác nhau của bộ sưu tập nhà để xe. Thí dụ:

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

Tôi đã nâng ý tưởng từ trang này: http://www.ai.uga.edu/~mc/SingleInstance.html


3
Sẽ không dễ dàng hơn để lưu trữ một bản sao được chia sẻ của nó trong lớp ứng dụng?
ross thẩm quyền

6

Có vẻ như có một cách thực sự tốt để xử lý việc này:

Ứng dụng sơ thẩm WPF

Điều này cung cấp một lớp mà bạn có thể thêm để quản lý tất cả các trường hợp đột biến và nhắn tin để đơn giản hóa việc triển khai của bạn đến mức đơn giản là tầm thường.


Điều này dường như không đưa cửa sổ hiện tại lên phía trước khi tôi thử nó.
RandomEngy

6

Mã sau đây là giải pháp ống có tên WCF của tôi để đăng ký một ứng dụng đơn lẻ. Thật tuyệt vì nó cũng phát sinh một sự kiện khi một cá thể khác cố gắng bắt đầu và nhận được dòng lệnh của cá thể khác.

Nó hướng đến WPF vì nó sử dụng System.Windows.StartupEventHandlerlớp, nhưng điều này có thể dễ dàng sửa đổi.

Mã này yêu cầu một tham chiếu đến PresentationFramework, và System.ServiceModel.

Sử dụng:

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

Mã nguồn:

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

Bạn không bao giờ nên sử dụng một mutex có tên để triển khai một ứng dụng đơn lẻ (hoặc ít nhất là không cho mã sản xuất). Mã độc hại có thể dễ dàng DoS ( Từ chối dịch vụ ) ass của bạn ...


8
"Bạn không bao giờ nên sử dụng một mutex có tên" - không bao giờ nói không bao giờ. Nếu mã độc đang chạy trên máy của tôi, có lẽ tôi đã bị lừa.
Joe

Trên thực tế, nó thậm chí không phải là mã độc hại. Nó chỉ có thể là một vụ va chạm tên tình cờ.
Matt Davison

Vậy thì bạn nên làm gì?
Kevin Berridge

Câu hỏi tốt hơn là lý do có thể bạn muốn hành vi đó. Đừng thiết kế ứng dụng của bạn dưới dạng một ứng dụng đơn lẻ =). Tôi biết đó là một câu trả lời khập khiễng nhưng từ quan điểm thiết kế thì nó hầu như luôn là câu trả lời đúng. Không biết nhiều hơn về ứng dụng, thật khó để nói nhiều hơn nữa.
Matt Davison

2
Ít nhất là trong Windows, Mutexes có quyền kiểm soát truy cập, vì vậy người ta có thể chơi với đối tượng của bạn. Để đặt tên cho các va chạm, đó là lý do tại sao UUID / GUID được phát minh ra.
NuSkooler

5

Nhìn vào mã theo dõi. Đây là một giải pháp tuyệt vời và đơn giản để ngăn chặn nhiều phiên bản của ứng dụng 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

Đây là những gì tôi sử dụng. Nó kết hợp phép liệt kê quy trình để thực hiện chuyển đổi và mutex để bảo vệ khỏi "các nhấp chuột hoạt động":

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

Tôi tìm thấy giải pháp đơn giản hơn, tương tự như của Dale Ragan, nhưng đã được sửa đổi một chút. Nó thực tế làm mọi thứ bạn cần và dựa trên lớp Microsoft WindowsFormsApplicationBase tiêu chuẩn.

Đầu tiên, bạn tạo lớp SingleInstanceControll, bạn có thể sử dụng trong tất cả các ứng dụng đơn lẻ khác, sử dụng Windows Forms:

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


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

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

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

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

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

Sau đó, bạn có thể sử dụng nó trong chương trình của bạn như sau:

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

Cả chương trình và giải pháp SingleInstanceControll_NET nên tham chiếu Microsoft.VisualBasic. Nếu bạn chỉ muốn kích hoạt lại ứng dụng đang chạy như một cửa sổ bình thường khi người dùng cố gắng khởi động lại chương trình đang chạy, tham số thứ hai trong SingleInstanceCont kiểm soát có thể là null. Trong ví dụ đã cho, cửa sổ được tối đa hóa.


4

Cập nhật 2017-01-25. Sau khi thử vài thứ, tôi quyết định dùng VisualBasic.dll, nó dễ dàng hơn và hoạt động tốt hơn (ít nhất là đối với tôi). Tôi để câu trả lời trước của tôi chỉ là tham khảo ...

Chỉ là tài liệu tham khảo, đây là cách tôi đã làm mà không truyền các đối số (mà tôi không thể tìm thấy bất kỳ lý do nào để làm như vậy ... Tôi có nghĩa là một ứng dụng duy nhất có các đối số được truyền từ cá thể này sang đối tượng khác). Nếu liên kết tệp là bắt buộc, thì một ứng dụng (theo kỳ vọng tiêu chuẩn của người dùng) sẽ được cung cấp cho mỗi tài liệu. Nếu bạn phải chuyển args cho ứng dụng hiện có, tôi nghĩ rằng tôi sẽ sử dụng vb dll.

Không vượt qua args (chỉ là ứng dụng đơn lẻ), tôi không muốn đăng ký một thông báo Window mới và không ghi đè lên vòng lặp thông báo như được định nghĩa trong Matt Davis Solution. Mặc dù việc thêm một dll VisualBasic không phải là vấn đề lớn, nhưng tôi không muốn thêm một tài liệu tham khảo mới chỉ để làm ứng dụng đơn lẻ. Ngoài ra, tôi thích tạo ra một lớp mới bằng Main thay vì gọi Shutdown từ App.Startup ghi đè để đảm bảo thoát ra càng sớm càng tốt.

Hy vọng rằng bất cứ ai cũng sẽ thích nó ... hoặc sẽ truyền cảm hứng một chút :-)

Lớp khởi động dự án nên được đặt là '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

Không sử dụng Mutex, câu trả lời đơn giản:

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

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

Đặt nó bên trong Program.Main().
Ví dụ :

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

Bạn có thể thêm MessageBox.Showvào if-statement và đặt "Ứng dụng đã chạy".
Điều này có thể hữu ích cho ai đó.


4
Nếu hai quá trình bắt đầu cùng một lúc, cả hai có thể thấy hai quá trình hoạt động và tự chấm dứt.
AT

@AT Đúng, điều này cũng có thể hữu ích cho các ứng dụng chạy với tư cách Quản trị viên hoặc người khác
newbieguy

Nếu bạn tạo một bản sao của ứng dụng của mình và đổi tên nó, bạn có thể chạy bản gốc và bản sao cùng một lúc.
Dominique bijnens

2

Các cách tiếp cận dựa trên tên được đặt tên không phải là đa nền tảng vì các trường hợp được đặt tên không phải là toàn cầu trong Mono. Các cách tiếp cận dựa trên quy trình không có bất kỳ sự đồng bộ hóa nào và có thể dẫn đến hành vi không chính xác (ví dụ: nhiều quá trình bắt đầu cùng một lúc có thể tự chấm dứt tùy thuộc vào thời gian). Các cách tiếp cận dựa trên hệ thống cửa sổ là không mong muốn trong một ứng dụng giao diện điều khiển. Giải pháp này, được xây dựng dựa trên câu trả lời của Divin, giải quyết tất cả các vấn đề sau:

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

Tôi sử dụng Mutex trong giải pháp của mình để ngăn chặn nhiều trường hợp.

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

Sử dụng giải pháp mutex:

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

Đây là một giải pháp nhẹ mà tôi sử dụng cho phép ứng dụng đưa một cửa sổ đã có sẵn lên nền trước mà không cần dùng đến các thông báo cửa sổ tùy chỉnh hoặc tìm kiếm tên quá trình một cách mù quáng.

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

Chỉnh sửa: Bạn cũng có thể lưu trữ và khởi tạo mutex và createdNew một cách tĩnh, nhưng bạn sẽ cần loại bỏ / giải phóng mutex một cách rõ ràng sau khi bạn hoàn thành nó. Cá nhân, tôi thích giữ mutex cục bộ vì nó sẽ tự động được xử lý ngay cả khi ứng dụng đóng mà không bao giờ kết thúc Main.



1

Tôi đã thêm một Phương thức sendMessage vào Lớp NativeMethods.

Rõ ràng công việc hiệu quả của phương thức postmessage, nếu ứng dụng không hiển thị trên thanh tác vụ, tuy nhiên sử dụng phương thức sendmessage sẽ giải quyết điều này.

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

Đây là điều tương tự được thực hiện thông qua Sự kiện.

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

[Tôi đã cung cấp mã mẫu cho các ứng dụng console và wpf bên dưới.]

Bạn chỉ phải kiểm tra giá trị của createdNewbiến (ví dụ bên dưới!), Sau khi bạn tạo phiên bản Mutex có tên.

Boolean createdNewsẽ trả về false:

nếu đối tượng Mutex có tên "YourApplicationNameHere" đã được tạo trên hệ thống ở đâu đó

Boolean createdNewsẽ trả về true:

nếu đây là Mutex đầu tiên có tên "YourApplicationNameHere" trên hệ thống.


Ứng dụng bảng điều khiển - Ví dụ:

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

Ví dụ về 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

Một giải pháp tiết kiệm thời gian cho C # Winforms ...

Chương trình.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

Vui lòng kiểm tra giải pháp đề xuất từ đây sử dụng semaphore để xác định xem một phiên bản hiện có đang chạy chưa, hoạt động cho ứng dụng WPF và có thể chuyển các đối số từ phiên bản thứ hai sang phiên bản đã chạy đầu tiên bằng cách sử dụng TcpListener và TcpClient:

Nó cũng hoạt động cho .NET Core, không chỉ cho .NET Framework.


1

Tôi không thể tìm thấy một giải pháp ngắn ở đây vì vậy tôi hy vọng ai đó sẽ thích điều này:

CẬP NHẬT 2018-09-20

Đặt mã này vào 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());
}

Điều này sẽ giới thiệu một điều kiện cuộc đua. Phải sử dụng một mutex.
georgiosd

1
không có gì đảm bảo rằng nếu bạn quay hai trường hợp cùng một lúc thì điều này sẽ hoạt động. Giống như cập nhật một biến từ hai chủ đề khác nhau. Lừa kinh doanh mạo hiểm. Sử dụng vũ lực, Luke :)
georgiosd

@georgiosd ah Tôi hiểu ý của bạn. Giống như nếu ai đó bắt đầu .exe và thay đổi tên. Vâng, đây sẽ là một cách để bắt đầu nhiều lần hơn, nhưng bình thường .exe không hoạt động, nếu tên được thay đổi. Tôi sẽ cập nhật câu trả lời của mình ^^ Cảm ơn bạn Luke: D đã chỉ ra điều đó :)
Deniz

1
Không chỉ là @Deniz. Nếu bạn bắt đầu hai quy trình thực sự nhanh, có khả năng danh sách quy trình hoặc phương thức tìm nạp chúng sẽ thực thi trong khi vẫn chỉ có một quy trình xuất hiện. Đây có thể là một trường hợp không liên quan đến bạn nhưng đây là một câu hỏi chung ...
georgiosd

@georgiosd Bạn có thể chứng minh điều đó không? Bởi vì Iv'e đã ​​thử nó chỉ cho bạn hehe. Nhưng điều đó là không thể đối với tôi, thậm chí thực sự "rất nhanh"! : P Vì vậy, tôi không thể hiểu tại sao bạn tin vào điều gì đó không phải là trường hợp và thậm chí không thích mã vô tội này: D
Deniz
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.