Tek örnekli bir WPF uygulaması oluşturmanın doğru yolu nedir?


657

.NET altında C # ve WPF kullanarak ( Windows Forms veya konsol yerine ), yalnızca tek bir örnek olarak çalıştırılabilecek bir uygulama oluşturmanın doğru yolu nedir?

Muteks adı verilen efsanevi bir şeyle ilgisi olduğunu biliyorum, nadiren bunlardan birinin ne olduğunu ve durmasını rahatsız eden birini bulabilirim.

Kodun, zaten çalışmakta olan örneği kullanıcının ikinci bir tane başlatmaya çalıştığını da bildirmesi ve varsa komut satırı bağımsız değişkenlerini iletmesi de gerekir.


14
Uygulama yine de sonlandırıldığında CLR otomatik olarak yayınlanmamış muteksleri serbest bırakmıyor mu?
Cocowalla

1
@Cocowalla: muteksin yönetilen uygulama tarafından oluşturulup oluşturulmadığını veya mevcut bir uygulamaya eklenip eklenmediğini bilemediği sürece sonlandırıcı yönetilmeyen muteksleri atmalıdır.
Ignacio Soler Garcia

Uygulamanızın yalnızca bir örneğine sahip olmak makul. Ancak zaten var olan bir uygulamaya argümanlar iletmek bana biraz aptalca geliyor. Bunu yapmak için hiçbir neden göremiyorum. Bir uygulamayı dosya uzantısıyla ilişkilendirirseniz, kullanıcının belgeleri açmak istediği kadar uygulamayı açmanız gerekir. Her kullanıcının beklediği standart davranış budur.
Eric Ouellet

9
@Cocowalla CLR yerel kaynakları yönetmez. Ancak, bir işlem sona ererse, tüm tanıtıcılar sistem tarafından serbest bırakılır (CLR tarafından değil OS).
2014'te

1
Cevabı @huseyint tercih ederim. Microsoft'un kendi 'SingleInstance.cs' sınıfını kullanır, bu nedenle Mutexes ve IntPtr'ler hakkında endişelenmenize gerek yoktur. Ayrıca, VisualBasic (yuk) bağımlılığı yok. Daha fazla bilgi için codereview.stackexchange.com/questions/20871/… adresine bakın ...
Heliac

Yanıtlar:


537

Mutex çözümü ile ilgili çok iyi bir makale . Makalede anlatılan yaklaşım iki nedenden dolayı avantajlıdır.

İlk olarak, Microsoft.VisualBasic derlemesine bağımlılık gerektirmez. Projem zaten bu toplantıya bağımlı olsaydı, muhtemelen başka bir cevapta gösterilen yaklaşımı kullanarak savunurdum . Ancak olduğu gibi, Microsoft.VisualBasic derlemesini kullanmıyorum ve projeme gereksiz bir bağımlılık eklemek istemiyorum.

İkinci olarak, makale, kullanıcı başka bir örneği başlatmaya çalıştığında uygulamanın mevcut örneğinin nasıl ön plana getirileceğini gösterir. Burada açıklanan diğer Mutex çözümlerinin ele almadığı çok hoş bir dokunuş.


GÜNCELLEME

01.08.2014 itibarıyla, yukarıda bağlantı verdiğim makale hala etkin, ancak blog bir süredir güncellenmedi. Bu, sonunda ortadan kaybolabileceği ve onunla birlikte savunulan çözümün beni endişelendirmesine neden oluyor. Buradaki makalenin içeriğini gelecek nesiller için yeniden üretiyorum. Kelimeler yalnızca Sanity Free Coding'deki blog sahibine aittir .

Bugün, uygulamamın kendisinin birden çok örneğini çalıştırmasını engelleyen bazı kodları yeniden düzenlemek istedim.

Daha önce işlem listesinde my myapp.exe örneğini aramak için System.Diagnostics.Process kullanmak vardı . Bu çalışırken, çok fazla yük getirir ve daha temiz bir şey istedim.

Bunun için bir muteks kullanabileceğini bilerek (ama daha önce hiç yapmamıştım) Kodumu kısmak ve hayatımı basitleştirmek için yola çıktım.

Uygulama ana sınıfında Mutex adlı bir statik oluşturdum :

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

Adlandırılmış bir muteks'e sahip olmak, aradığım sihir olan birden çok iş parçacığı ve işlem arasında senkronizasyonu biriktirmemizi sağlar.

Mutex.WaitOne , beklememiz için belirli bir süre belirten bir aşırı yüke sahiptir. Aslında kodumuzu senkronize etmek istemediğimizden (daha fazlası sadece kullanımda olup olmadığını kontrol edin) aşırı yükü iki parametre ile kullanıyoruz: Mutex.WaitOne (Zaman aralığı zaman aşımı, bool exitContext) . Biri girebiliyorsa true değerini döndürürse, false değerini döndürür. Bu durumda, hiç beklemek istemiyoruz; Muteksimiz kullanılıyorsa, atlayın ve devam edin, bu yüzden TimeSpan.Zero'ya geçiyoruz (0 milisaniye bekleyin) ve bir kilit elde etmeye çalışmadan önce senkronizasyon bağlamından çıkabilmemiz için exitContext'i true değerine ayarlayın. Bunu kullanarak, Application.Run kodumuzu şöyle bir şey içine sarıyoruz:

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

Uygulamamız çalışıyorsa, WaitOne yanlış döndürür ve bir mesaj kutusu alırız.

Bir ileti kutusu göstermek yerine, çalışan örneğimi birisinin zaten çalıştığını unuttuğunu bildirmek için küçük bir Win32 kullanmayı seçtim (diğer tüm pencerelerin üstüne getirerek). Bunu elde etmek için her pencereye özel bir mesaj yayınlamak için PostMessage kullandım (özel mesaj, çalışan uygulamam tarafından RegisterWindowMessage'a kaydedildi , bu da sadece benim uygulamamın ne olduğunu biliyor demektir) sonra ikinci örneğim çıkar. Çalışan uygulama örneği bu bildirimi alır ve işler. Bunu yapmak için, ana formumda WndProc'u aştım ve özel bildirimimi dinledim. Bu bildirimi aldığımda, formun TopMost özelliğini en üste getirmek için true olarak ayarladım.

İşte sonuçta:

  • 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 (ön taraf kısmi)
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
Bu cevabın daha az kod ve daha az kütüphane kullanması ve en üst düzeye çıkarmayı sağlaması temelinde, bunu yeni kabul edilen cevap yapacağım. Herhangi biri API'leri kullanarak formu en üste taşımanın daha doğru bir yolunu biliyorsanız, bunu eklemekten çekinmeyin.
Nidonocu

11
@MaviRaja, ilk uygulama örneğini başlatırsınız. İkinci uygulama örneğini başlattığınızda, başka bir örneğin zaten çalıştığını algılar ve kapanmaya hazırlanır. Bunu yapmadan önce, ilk örneğe bir "SHOWME" yerel iletisi gönderir ve ilk örneği en üste getirir. .NET'teki olaylar süreçler arası iletişime izin vermez, bu nedenle yerel mesaj kullanılır.
Matt Davis

7
Komut satırlarını diğer örnekten geçirmenin bir yolu var mı?
gyurisc

22
@Nam, Mutexyapıcı sadece bir dize gerektirir, böylece istediğiniz herhangi bir dize adı sağlayabilirsiniz, örneğin "Bu Benim Muteksim". 'Mutex' diğer işlemlerde kullanılabilen bir sistem nesnesi olduğundan, genellikle aynı sistemdeki diğer 'Mutex' adlarıyla çakışmaması için adın benzersiz olmasını istersiniz. Makalede, şifreli görünümlü dize bir 'Rehber' dir. Bunu arayarak bunu programlı olarak oluşturabilirsiniz System.Guid.NewGuid(). Makale durumunda, kullanıcı muhtemelen burada gösterildiği gibi Visual Studio üzerinden oluşturmuştur: msdn.microsoft.com/en-us/library/ms241442(VS.80).aspx
Matt Davis

6
Mutex yaklaşımı aynı kullanıcının uygulamayı tekrar başlatmaya çalıştığını varsayar mı? Kesinlikle "uygulamanın mevcut örneğini ön plana çıkarmak" bir 'anahtar kullanıcı' sonra mantıklı değil
dumbledad

107

Mutex sınıfını kullanabilirsiniz, ancak kısa bir süre sonra argümanları ve benzeri bilgileri iletmek için kodu uygulamanız gerekeceğini öğreneceksiniz. Chris Sell'in kitabını okuduğumda WinForms'da programlama yaparken bir numara öğrendim . Bu hile, çerçeve içinde bizim için zaten mevcut olan mantığı kullanır. Seni bilmiyorum, ama çerçevede yeniden kullanabileceğim şeyleri öğrendiğimde, genellikle tekerleği yeniden icat etmek yerine izlediğim yoldur. Tabii ki istediğim her şeyi yapmazsa.

WPF'ye girdiğimde, aynı kodu kullanmanın bir yolunu buldum, ancak bir WPF uygulamasında. Bu çözüm, sorunuza dayalı olarak ihtiyaçlarınızı karşılamalıdır.

İlk olarak, uygulama sınıfımızı oluşturmamız gerekiyor. Bu sınıfta OnStartup olayını geçersiz kılacağız ve daha sonra kullanılacak olan Activate adlı bir yöntem oluşturacağız.

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

İkincisi, örneklerimizi yönetebilecek bir sınıf yaratmamız gerekecek. Bunu yapmadan önce, aslında Microsoft.VisualBasic derlemesindeki bazı kodları yeniden kullanacağız. Bu örnekte C # kullanıyorum, derlemeye bir başvuru yapmak zorunda kaldı. VB.NET kullanıyorsanız, hiçbir şey yapmanız gerekmez. Kullanacağımız sınıf WindowsFormsApplicationBase'dir ve örnek yöneticimizi devralır ve daha sonra tek örneklemeyi işlemek için özellikler ve olaylardan yararlanır.

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

Temel olarak, VB bitlerini tek bir örneği tespit etmek ve buna göre işlemek için kullanıyoruz. İlk örnek yüklendiğinde OnStartup tetiklenir. Uygulama yeniden çalıştırıldığında OnStartupNextInstance tetiklenir. Gördüğünüz gibi, olay argümanları aracılığıyla komut satırında nelerin geçtiğini anlayabiliyorum. Değeri bir örnek alanına ayarladım. Komut satırını burada ayrıştırabilir veya yapıcı ve Etkinleştir yönteminin çağrısı yoluyla uygulamanıza iletebilirsiniz.

Üçüncü olarak, EntryPoint'imizi yaratmanın zamanı geldi. Uygulamayı normalde yaptığınız gibi yenilemek yerine, SingleInstanceManager'ımızdan faydalanacağız.

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

Umarım her şeyi takip edebilir ve bu uygulamayı kullanabilir ve kendiniz yapabilirsiniz.


9
Muteks çözümüne sadık kalırdım çünkü formlarla ilgisi yoktur.
Steven Sudit

1
Bunu kullandım çünkü diğer yaklaşımlarla ilgili sorunlarım vardı, ancak başlık altında uzaktan kumanda kullandığından eminim. Uygulamamın iki ilgili sorunu vardı - bazı müşteriler, söylememelerine rağmen eve telefon etmeye çalıştığını söylüyor. Daha dikkatli baktıklarında, yerel ana bilgisayarla bağlantıdır. Yine de başlangıçta bunu bilmiyorlar. Ayrıca, remoting'i farklı bir amaç için kullanamıyorum (sanırım?) Çünkü zaten bunun için kullanılıyor. Muteks yaklaşımını denediğimde, uzaktan tekrar kullanabilirim.
Richard Watson

4
Beni affet, ama bir şey eksik olmadıkça, 3 satır kod yazmaktan kaçındın ve bunun yerine sadece oldukça ağır kod yazmak için çerçeveyi yeniden kullandın. Peki tasarruflar nerede?
greenoldman

2
Winformlarda yapmak mümkün mü?
Jack

1
Uygulama örneğinde InitializeComponent () yöntemini çağırmazsanız, kaynakları çözümleyemezsiniz ... _application = new SingleInstanceApplication (); _application.InitializeComponent (); _application.Run ();
Nick

84

Gönderen burada .

Çapraz işlem Mutex'in yaygın bir kullanımı, aynı anda yalnızca bir program örneğinin çalışabilmesini sağlamaktır. İşte böyle yapılır:

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'in iyi bir özelliği, uygulama ilk olarak ReleaseMutex çağrılmadan sona ererse, CLR'nin Mutex'i otomatik olarak serbest bırakmasıdır.


5
Söylemeliyim ki, bu cevabı sadece WinForms'a bağlı olmadığı için kabul edilen yanıttan çok daha fazla seviyorum. Kişisel olarak gelişimimin çoğu WPF'ye taşındı ve böyle bir şey için WinForm kitaplıklarını çekmek istemiyorum.
Switters

5
Tabii ki, tam bir cevap olarak, argümanları diğer örneğe geçirmeyi de tanımlamanız gerekir :)
Simon Buchan

@ Jason, iyi, teşekkürler! Ama zaman aşımını geçmemeyi tercih ederim. Çok öznel ve çok değişkene bağlı. Başka bir uygulamanın başlamasını etkinleştirmek istiyorsanız, muteksinizi daha hızlı serbest bırakın. Örneğin, kullanıcı kapatmayı onaylar onaylamaz
Eric Ouellet 21

@EricOuellet: Sekmeleri olan hemen hemen her program bunu yapar - Photoshop, Sublime Text, Chrome .... "Ana" bir işlem yapmak için iyi bir nedeniniz varsa (ayarlar için pro-pro DB'niz olduğunu varsayabilirsiniz) kullanıcı arayüzünü yeni bir süreçmiş gibi göstermesini istemek.
Simon Buchan

@Simon, haklısın. Kendimi çok eski bir şey hakkında sorguladım ... MDI vs SDI (Çoklu belge arayüzü vs Tek belge arayüzü). Sekmeler hakkında konuşurken MDI'ya başvurursunuz. 1998'de bir Microsoft kitabı her MDI uygulamasını kaldırmayı önerir. Microsoft, Word, Excel ... 'i daha basit ve daha iyi olduğunu düşündüğüm SDI'ya geçirdi. Chrome ve diğerlerinin (şimdi IE) MDI'ya geri dönmek istediğini anlıyorum. Ben dosya assoc seçildiğinde yeni bir uygulama açmak için hala daha iyi olduğunu (hiçbir şey / kişisel duygular dayalı) personnaly. Ama şimdi sorulan soruyu daha iyi anlıyorum. Teşekkürler !
Eric Ouellet

58

MSDN aslında C # ve VB için tam olarak bunu yapmak için örnek bir uygulama var: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx

Tek örnek algılamayı geliştirmenin en yaygın ve güvenilir tekniği Microsoft .NET Framework uzaktan erişim altyapısını (System.Remoting) kullanmaktır. Microsoft .NET Framework (sürüm 2.0), gerekli uzaktan kumanda işlevini içeren bir WindowsFormsApplicationBase türü içerir. Bu türü bir WPF uygulamasına dahil etmek için, bir türün türetilmesi ve uygulama statik giriş noktası yöntemi, Main ve WPF uygulamasının Uygulama türü arasında bir dolgu olarak kullanılması gerekir. Şim, bir uygulamanın ilk başlatıldığını ve sonraki başlatmalar denendiğinde algılar ve başlatmaları nasıl işleyeceğini belirlemek için WPF Uygulama türünü kontrol eder.

  • C # insanlar için sadece derin bir nefes alın ve 'VisualBasic DLL dahil etmek istemiyorum' bütün unutun. Yüzünden bu ve ne Scott Hanselman diyor ve bu hemen hemen soruna en temiz çözüm ve daha çerçevede hakkında senden daha çok şey bilen insanlar tarafından tasarlanmış olmasıdır.
  • Kullanılabilirlik açısından, kullanıcı bir uygulamayı yüklüyorsa ve zaten açıksa ve onlara 'Another instance of the app is running. Bye'çok mutlu bir kullanıcı olmayacakları gibi bir hata mesajı veriyorsanız . Basitçe (bir GUI uygulamasında) o uygulamaya geçmeniz ve sağlanan argümanları iletmeniz gerekir - ya da komut satırı parametrelerinin bir anlamı yoksa, küçültülmüş olabilecek uygulamayı açmanız gerekir.

Çerçeve zaten bu konuda destek var - sadece bazı aptal DLL adlı Microsoft.VisualBasicve içine koymak Microsoft.ApplicationUtilsya da böyle bir şey alamadım . Üstesinden gelin ya da Reflektörü açın.

İpucu: Bu yaklaşımı aynen olduğu gibi kullanırsanız ve zaten kaynaklar vb. İçeren bir App.xaml'iniz varsa buna da göz atmak istersiniz .


'Buna da bir göz atın' bağlantısını eklediğiniz için teşekkür ederiz. Tam da ihtiyacım olan şey bu. Bu arada, bağlantınızdaki # 3 çözümü en iyisidir.
Sonsuz21

Ayrıca, mümkün olduğunca çerçeveye ve özel olarak tasarlanmış kütüphanelere delege edilmenin savunucusuyum.
Eniola

23

Bu kod ana yönteme gitmelidir. WPF'deki ana yöntem hakkında daha fazla bilgi için buraya bakın .

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

Yöntem 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(...);
    }
}

Not: Yukarıdaki yöntemler, işleminizin / uygulamanızın benzersiz bir ada sahip olduğunu varsayar. Çünkü mevcut işlemcilerin olup olmadığını bulmak için işlem adını kullanır. Dolayısıyla, uygulamanızın çok yaygın bir adı varsa (örneğin: Not Defteri), yukarıdaki yaklaşım işe yaramaz.


1
Ayrıca, bilgisayarınızda aynı ada sahip başka bir program çalışıyorsa bu çalışmaz. ProcessNameeksi yürütülebilir dosya adını döndürür exe. "Not Defteri" adlı bir uygulama yaparsanız ve Windows not defteri çalışıyorsa, uygulamanız çalışırken algılar.
Jcl

1
Bu cevap için teşekkürler. Birçok benzer soru buldum ve cevaplar her zaman çok ayrıntılı ve / veya kafa karıştırıcıydı. Bu (Yöntem # 1) açık, net ve en önemlisi aslında benim kod çalıştırmak yapmak yardımcı oldu.
ElDoRado1239

20

Çoğu kullanım durumu için kolayca çalışan tek kullanımlık bir Sınıfım var:

Şöyle kullanın:

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

        // Initialize program here.
    }
}

İşte burada:

/// <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
bu işe başlamak oldukça kolay oldu. Ben Application.Exit () değiştirinceye kadar ikinci uygulamayı kapatmaz; basit bir dönüşe; ama bunun dışında harika. Her ne kadar bir arayüz kullandığından, önceki çözüme daha yakından bakacağımı itiraf etmeliyim. blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
hal9000

15

Mutex ve IPC öğelerini kullanan ve aynı zamanda çalışan örneğe herhangi bir komut satırı argümanı ileten yeni bir WPF Tek Örnek Uygulamasıdır .


Bunu büyük bir başarıyla kullanıyorum. NamedPipes'ı buna eklerseniz, komut satırı bağımsız değişkenlerini orijinal uygulamaya da iletebilirsiniz. 'SingleInstance.cs' sınıfı Microsoft tarafından yazılmıştır. Arik Poznanski'nin CodeProject blogunun daha okunabilir bir sürümüne başka bir bağlantı ekledim.
Heliac

Bağlantı kesildi.
Mike Lowery

11

C # .NET Tek Örnek Uygulaması koduİşaretli cevap için referans harika bir başlangıçtır.

Ancak, zaten var olan örneğin kalıcı bir iletişim kutusunun açık olduğu, bu iletişim kutusunun yönetilen (yaklaşık kutu gibi başka bir Form gibi) veya yönetilmeyen ( Standart .NET sınıfını kullanırken bile OpenFileDialog). Orijinal kodla, ana form etkinleştirilir, ancak kalıcı olan tuhaf kalır, bu da garip görünüyor, ayrıca kullanıcı uygulamayı kullanmaya devam etmek için üzerine tıklamalıdır.

Yani, tüm bu Winforms ve WPF uygulamaları için tamamen otomatik olarak işlemek için bir SingleInstance yardımcı sınıf oluşturduk.

Winformlar :

1) Program sınıfını şu şekilde değiştirin:

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) ana pencere sınıfını şu şekilde değiştirin:

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) Uygulama sayfasını bu şekilde değiştirin (ve Ana yöntemini yeniden tanımlayabilmek için derleme işlemini sayfaya ayarladığınızdan emin olun):

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) ana pencere sınıfını şu şekilde değiştirin:

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

Ve yardımcı program sınıfı:

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

Aşağıda, uygulamanın tek bir örneğine sahip olmanıza olanak tanıyan bir örnek verilmiştir. Herhangi bir yeni örnek yüklendiğinde, argümanlarını çalışan ana örneğe iletirler.

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

Bu ne yapacağımın gerçekten güzel bir örneğidir. Nathan, tüm argümanlar bu yöntemle mi gönderiliyor? Benim app 7 ya da öylesine var ve bu kod çalışacağını düşünüyorum .
kevp

1
Örneğimde sadece ilk argüman gönderilir, ancak hepsi gönderilecek şekilde değiştirilebilir.
Nathan Moinvaziri

8

Sadece bazı düşünceler: Bir uygulamanın sadece bir örneğinin inandığınız gibi "topal" olmamasını gerektiren durumlar vardır. Veritabanı uygulamaları, vb. Tek bir kullanıcının bir veritabanına erişmesi için uygulamanın birden fazla örneğine izin veriyorsa (kullanıcıların bildiğiniz gibi, uygulamanın birden çok örneğinde açık olan tüm kayıtları güncelleyen tüm bunların güncellenmesi daha zordur) makine vb.). İlk olarak, "isim çarpışma olayı için, insan tarafından okunabilir bir ad kullanmayın - bunun yerine bir GUID kullanın veya daha iyi bir GUID + insan tarafından okunabilir ad. Ad çakışması olasılığı radardan düştü ve Mutex umursamıyor Birisinin işaret ettiği gibi, bir DOS saldırısı berbat olurdu, ancak kötü niyetli kişi muteks adını alma ve uygulamalarına dahil etme sorununa giderse, Zaten bir hedefsiniz ve kendinizi korumak için bir muteks adını keman yapmaktan çok daha fazlasını yapmanız gerekecek. Ayrıca, biri şu şekilde kullanılırsa: yeni Mutex (true, "bazı GUID artı Adı", AIsFirstInstance dışında), Mutex'in ilk örnek olup olmadığı konusunda göstergeniz zaten vardır.


6

Bu kadar basit bir soruya pek çok cevap var. Burada bir şeyleri biraz sarsmak, bu soruna benim çözümüm.

Mutex oluşturmak zahmetli olabilir, çünkü JIT-er kodunuzu sadece kodunuzun küçük bir kısmı için kullandığını görür ve çöp toplama için hazır olarak işaretlemek ister. Bu Mutex'i bu kadar uzun süre kullanmayacağınızı düşünerek sizi akıllıca düşünmek istiyor. Gerçekte, uygulamanız çalıştığı sürece bu Mutex'e asmak istersiniz. Çöp toplayıcıya Mutex'i yalnız bırakmasını söylemenin en iyi yolu, onu farklı nesil garaj koleksiyonundan dışarıda tutmasını söylemektir. Misal:

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

Fikri şu sayfadan kaldırdım: http://www.ai.uga.edu/~mc/SingleInstance.html


3
Paylaşılan bir kopyasını uygulama sınıfında saklamak daha kolay olmaz mıydı?
rossisdead

6

Bunu ele almanın gerçekten iyi bir yolu var gibi görünüyor:

WPF Tek Örnek Uygulaması

Bu, uygulamanızı basitçe önemsiz olan noktaya basitleştirmek için tüm muteksi ve mesajlaşma işlerini yöneten ekleyebileceğiniz bir sınıf sağlar.


Bu, denediğimde mevcut pencereyi ön plana getirmedi.
RandomEngy

6

Aşağıdaki kod, tek örnekli bir uygulamayı kaydetmek için benim WCF adlı borular çözümdür. Bu hoş bir şey çünkü başka bir örnek başlatmaya çalıştığında bir olayı da yükseltiyor ve diğer örneğin komut satırını alıyor.

WPF'ye yöneliktir, çünkü System.Windows.StartupEventHandlerSınıfı , ancak bu kolayca değiştirilebilir.

Bu kod PresentationFramework, ve öğelerine başvuru gerektirir System.ServiceModel.

Kullanımı:

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

Kaynak kodu:

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

Tek örnekli bir uygulamayı uygulamak için (veya en azından üretim kodu için değil) asla adlandırılmış bir muteks kullanmamalısınız. Kötü amaçlı kod kolayca DoS ( Hizmet Reddi ) kıçını yapabilirsiniz ...


8
"Asla adlandırılmış muteks kullanmamalısınız" - asla asla deme. Makinemde kötü amaçlı kod çalışıyorsa, muhtemelen zaten kullanılıyorum.
Joe

Aslında kötü niyetli bir kod olması bile gerekmiyor. Sadece kazara bir isim çarpışması olabilir.
Matt Davison

O zaman ne yapmalısın?
Kevin Berridge

Daha iyi olan soru, bu davranışın ne gibi bir neden olmasını istediğinizdir. Uygulamanızı tek örnekli bir uygulama olarak tasarlamayın =). Bunun topal bir cevap olduğunu biliyorum ama tasarım açısından neredeyse her zaman doğru cevap. Uygulaması hakkında daha fazla bilmeden çok daha söylemek zor.
Matt Davison

2
En azından Windows altında, Mutexes'in erişim kontrolü vardır, böylece biri nesnenizle oyuncak olabilir. Çarpışmaları kendileri adlandırmak için, bu yüzden icat edilen UUID / GUID'ler.
NuSkooler

5

Aşağıdaki koda bak. Bir WPF uygulamasının birden fazla örneğini önlemek için harika ve basit bir çözümdür.

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

İşte kullandığım. "Etkin tıklayıcılardan" korunmak için anahtarlama ve muteksi gerçekleştirmek için işlem numaralandırmasını birleştirdi:

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'ınkine benzer daha basit bir çözüm buldum, ancak biraz değiştirdim. Pratik olarak ihtiyacınız olan her şeyi yapar ve standart Microsoft WindowsFormsApplicationBase sınıfına dayanır.

İlk olarak, Windows Forms kullanan diğer tüm tek örnekli uygulamalarda kullanabileceğiniz SingleInstanceController sınıfı oluşturursunuz:

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

Daha sonra programınızda aşağıdaki gibi kullanabilirsiniz:

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

Hem program hem de SingleInstanceController_NET ​​çözümü Microsoft.VisualBasic'e başvurmalıdır. Kullanıcı çalışan programı yeniden başlatmaya çalıştığında çalışan uygulamayı normal bir pencere olarak yeniden etkinleştirmek isterseniz, SingleInstanceController öğesindeki ikinci parametre null olabilir. Verilen örnekte, pencere ekranı kaplıyor.


4

Güncelleme 2017-01-25. Birkaç şey denedikten sonra, VisualBasic.dll ile daha kolay ve daha iyi çalışır (en azından benim için) gitmeye karar verdi. Önceki cevabımı referans olarak verdim ...

Referans olarak, argümanları geçmeden bu şekilde yaptım (bunu yapmak için herhangi bir neden bulamıyorum ... Bir örnekten diğerine aktarılacak argümanlarla tek bir uygulama demek istiyorum). Dosya ilişkilendirmesi gerekiyorsa, her bir doküman için bir uygulama (kullanıcı başına standart beklenti) başlatılmalıdır. Var olan app args geçmek zorunda, ben vb dll kullanılır düşünüyorum.

Args (sadece tek örnek uygulaması) geçirmiyorum, yeni bir Window mesajı kaydetmemeyi ve Matt Davis Çözümünde tanımlandığı gibi mesaj döngüsünü geçersiz kılmamayı tercih ediyorum. Bir VisualBasic dll eklemek için büyük bir anlaşma olmasa da, ben sadece tek örnek uygulaması yapmak için yeni bir referans eklemeyi tercih ederim. Ayrıca, en kısa sürede çıkmak için App.Startup geçersiz kılma çağrısı yerine Main ile yeni bir sınıf başlatmak tercih ediyorum.

Umarım herkes beğenir ... ya da biraz ilham verir :-)

Proje başlangıç ​​sınıfı 'SingleInstanceApp' olarak ayarlanmalıdır.

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'i kullanmamak basit cevap:

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

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

İçine koyun Program.Main().
Örnek :

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

Ekleyebilir MessageBox.Showiçin if-Bildirim ve "Uygulama zaten çalışıyor" koydu.
Bu birisi için yararlı olabilir.


4
İki işlem aynı anda başlarsa, ikisi de iki etkin işlem görebilir ve kendi kendine sonlanabilir.
AT

@AT Evet, bu Yönetici veya başka çalışan uygulamalar için de yararlı olabilir
newbieguy

Uygulamanızın bir kopyasını alıp yeniden adlandırırsanız, orijinali ve kopyayı aynı anda çalıştırabilirsiniz.
Dominique Bijnens

2

Adlandırılmış muteks tabanlı yaklaşımlar platformlar arası değildir çünkü adlandırılan muteksler Mono'da küresel değildir. Süreç numaralandırma tabanlı yaklaşımlar herhangi bir senkronizasyona sahip değildir ve yanlış davranışa neden olabilir (örneğin, aynı anda başlatılan birden çok işlemin tümü zamanlamaya bağlı olarak kendi kendine sonlanabilir). Bir konsol uygulamasında pencereleme sistemi tabanlı yaklaşımlar istenmez. Divin'in cevabı üzerine inşa edilen bu çözüm, tüm bu sorunları ele alıyor:

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

Birden fazla örneği önlemek için çözümümde Mutex kullanıyorum .

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

Muteks çözümünü kullanın:

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

İşte uygulamanın özel pencere mesajlarına başvurmadan veya körü körüne işlem adlarını aramadan mevcut bir pencereyi ön plana getirmesine izin veren hafif bir çözüm.

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

Düzenleme: Ayrıca muteksi depolayabilir ve başlatabilirsiniz ve createNew statik olarak, ancak muteksi bitirdikten sonra açıkça atmanız / bırakmanız gerekir. Şahsen, uygulama Main'in sonuna ulaşmadan kapansa bile muteksi yerel tutmayı tercih ederim.



1

NativeMethods Sınıfına bir sendMessage Yöntemi ekledim.

Görünüşe göre uygulama görev çubuğunda gösterilmiyorsa postmessage yöntemi dosent çalışır, ancak sendmessage yöntemini kullanmak bunu çözer.

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

İşte aynı şey Event üzerinden uygulandı.

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

[Aşağıdaki konsol ve wpf uygulamaları için örnek kod sağladım.]

Sadece createdNewDeğişkenin adlandırılmış Mutex örneğini oluşturduktan sonra (aşağıdaki örnek!).

Boolean createdNewdönecektir yanlış:

"UygulamanızınAdı Burada" adlı Mutex örneği sistemde zaten bir yerde oluşturulmuşsa

Boole doğrucreatedNew dönecektir :

bu sistemdeki "UygulamaAdınız Burada" adlı ilk Muteks ise.


Konsol uygulaması - Örnek:

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 Örnek:

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 için zaman kazandıran bir çözüm ...

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

Lütfen önerilen çözümü buradan kontrol edinMevcut bir örneğin zaten çalışıp çalışmadığını belirlemek için bir semafor kullanan bir WPF uygulaması için çalışıyor ve bir TcpListener ve bir TcpClient kullanarak argümanları ikinci örnekten halihazırda çalışan ilk örneğe aktarabilir:

Yalnızca .NET Framework için değil, .NET Core için de çalışır.


1

Burada kısa bir çözüm bulamıyorum, umarım birisi bunu beğenir:

GÜNCELLEME 2018-09-20

Bu kodu şuna ekle 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());
}

Bu bir yarış koşulu getirecektir. Muteks kullanılmalıdır.
georgiosd

1
aynı anda iki örneği döndürürseniz, bunun işe yarayacağının garantisi yoktur. İki farklı evreden bir değişkeni güncellemek gibi. Zor riskli iş. Gücü kullan, Luke :)
georgiosd

@georgiosd ah Ne demek istediğini anlıyorum. Birisi .exe başlatılır ve adı değiştirir gibi. Evet, daha fazla kez başlatmanın bir yolu olurdu, ancak isim değiştiyse normalde .exe çalışmaz. Cevabımı güncelleyeceğim ^^ Teşekkür ederim Luke: D, işaret ettiğin için :)
Deniz

1
Sadece @Deniz değil. İki işlemi gerçekten hızlı bir şekilde başlatırsanız, işlem listesi veya bunları getiren yöntemin hala tek bir tane varken yürütme olasılığı vardır. Bu sizin için önemsiz bir durum olabilir, ancak bu genel bir soru ...
georgiosd

@georgiosd Bunu kanıtlayabilir misiniz? Çünkü Iv'e sadece senin için test etti. Ama benim için mümkün değildi, hatta gerçekten çok hızlı! : P Öyleyse neden böyle olmayan bir şeye inandığını ve hatta bu masum kodu sevmediğini anlayamıyorum: D
Deniz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.