باستخدام مزامنة لمنع مثيلات متعددة من نفس البرنامج من تشغيل آمن ؟

StackOverflow https://stackoverflow.com/questions/646480

سؤال

أنا باستخدام هذا الكود لمنع الدرجة الثانية من البرنامج من تشغيل في نفس الوقت, هل هو آمن ؟

Mutex appSingleton = new System.Threading.Mutex(false, "MyAppSingleInstnceMutx");
if (appSingleton.WaitOne(0, false)) {
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
    appSingleton.Close();
} else {
    MessageBox.Show("Sorry, only one instance of MyApp is allowed.");
}

أنا قلق من أنه إذا ما يطرح استثناء و تعطل التطبيق أن مزامنة لا يزال عقده.هل هذا صحيح ؟

هل كانت مفيدة؟

المحلول

بشكل عام نعم هذا العمل.لكن الشيطان يكمن في التفاصيل.

أولا تريد إغلاق مزامنة في finally كتلة.وإلا فإن العملية يمكن أن إنهاء فجأة وترك الأمر في إشارة الدولة ، مثل استثناء.التي من شأنها أن تجعل من ذلك أن مستقبل عملية الحالات لن يكون قادرا على البدء.

على الرغم من سوء الحظ ، حتى مع وجود finally كتلة يجب أن تتعامل مع احتمال أنه سيتم إنهاء العملية دون تحرير مزامنة.هذا يمكن أن يحدث على سبيل المثال إذا كان المستخدم يقتل العملية من خلال يبرد.هناك شرط سباق في التعليمات البرمجية الخاصة بك التي من شأنها أن تسمح العملية الثانية للحصول على AbandonedMutexException في WaitOne المكالمة.سوف تحتاج إلى استراتيجية الإنعاش عن هذا.

أنا أشجعكم على قراءة تفاصيل مزامنة الدرجة.وذلك باستخدام أنها ليست دائما بسيطة.


وتتوسع في حالة تعارض إمكانية:

التسلسل التالي من الأحداث يمكن أن تحدث والتي من شأنها أن تتسبب في الدرجة الثانية من التطبيق إلى رمي:

  1. طبيعي عملية بدء التشغيل.
  2. العملية الثانية يبدأ و aquires مقبض إلى مزامنة ولكن يتم تشغيل من قبل WaitOne المكالمة.
  3. عملية #1 أجهضت.مزامنة وليس للتدمير بسبب عملية #2 مقبض.بدلا من ذلك تعيين مهجور الدولة.
  4. العملية الثانية يبدأ تشغيلها مرة أخرى و يحصل على AbanonedMutexException.

نصائح أخرى

ومن أكثر من المعتاد ومريحة للاستخدام الأحداث ويندوز لهذا الغرض. منها مثلا.

static EventWaitHandle s_event ;

bool created ;
s_event = new EventWaitHandle (false, 
    EventResetMode.ManualReset, "my program#startup", out created) ;
if (created) Launch () ;
else         Exit   () ;

عند إنهاء العملية أو ينهي، ويندوز إغلاق هذا الحدث بالنسبة لك، وتدميره إذا بقيت أي مؤشرات مفتوحة.

واضاف : لإدارة الجلسات، واستخدام Local\ والبادئات Global\ ل(أو مزامنة) اسم الحدث. إذا كان التطبيق الخاص بك هو لكل مستخدم، مجرد إلحاق اسم المهترئ مناسب المستخدم بتسجيل الدخول إلى اسم الحدث.

يمكنك استخدام مزامنة, ولكن تأكد أولا من أن هذا هو حقا ما تريد.

لأن "تجنب حالات متعددة" ليست محددة بوضوح.فإنه يمكن أن يعني

  1. تجنب حالات متعددة بدأت في نفس جلسة عمل المستخدم ، بغض النظر عن كيفية العديد من أجهزة الكمبيوتر المكتبية المستخدم الدورة ، ولكن يسمح مثيلات متعددة لتشغيل بشكل متزامن في مختلف جلسات عمل المستخدم.
  2. تجنب حالات متعددة بدأت في نفس سطح المكتب ، ولكن يسمح مثيلات متعددة لتشغيل طالما كل واحد في سطح المكتب منفصلة.
  3. تجنب حالات متعددة بدأت بنفس حساب المستخدم ، بغض النظر عن كيفية العديد من أجهزة الكمبيوتر المكتبية أو الدورات التي تعمل تحت هذا الحساب موجودا ، ولكن يسمح مثيلات متعددة لتشغيل بشكل متزامن على دورات قيد التشغيل ضمن حساب مستخدم مختلف.
  4. تجنب حالات متعددة بدأت على نفس الجهاز.هذا يعني أنه لا يهم كيف العديد من أجهزة الكمبيوتر المكتبية يتم استخدامها من قبل عدد التعسفي من المستخدمين, يمكن أن يكون هناك في أكثر من مثيل واحد من برنامج التشغيل.

باستخدام مزامنة أنت أساسا باستخدام تحديد رقم 4.

وأنا استخدم هذا الأسلوب، وأعتقد أن تكون آمنة ليتم تدمير المزامنة إذا لم يكن منذ فترة طويلة من قبل أي تطبيق (ويتم إنهاء التطبيقات إذا إذا لم يتمكنوا من خلق في البداية Mutext). هذا قد أو قد لا تعمل بشكل مماثل في "AppDomain-العمليات" (انظر الرابط في الأسفل):

// Make sure that appMutex has the lifetime of the code to guard --
// you must keep it from being collected (and the finalizer called, which
// will release the mutex, which is not good here!).
// You can also poke the mutex later.
Mutex appMutex;

// In some startup/initialization code
bool createdNew;
appMutex = new Mutex(true, "mutexname", out createdNew);
if (!createdNew) {
  // The mutex already existed - exit application.
  // Windows will release the resources for the process and the
  // mutex will go away when no process has it open.
  // Processes are much more cleaned-up after than threads :)
} else {
  // win \o/
}

ويعاني أعلاه من من الملاحظات في إجابات أخرى / تعليقات حول البرامج الخبيثة أن تكون قادرة على الجلوس على مزامنة. ليس مصدر قلق هنا. أيضا، مزامنة غير المسبوقة ببادئة خلق في الفضاء "المحلي". وربما كان هذا من اليمين شيء هنا.

وانظر: HTTP: // ayende.com/Blog/archive/2008/02/28/The-mysterious-life-of-mutexes.aspx - يأتي مع جون السكيت؛ -)

على النوافذ إنهاء العملية على النتائج التالية:

  • أي المواضيع المتبقية في هذه العملية يتم وضع علامة من أجل إنهاء.
  • أي الموارد المخصصة من قبل العملية يتم تحرير.
  • كل نواة الكائنات مغلقة.
  • عملية كود يتم إزالتها من الذاكرة.
  • رمز إنهاء عملية تعيين.
  • كائن العملية تتم الإشارة.

مزامنة كائنات هي لب الأشياء ، لذلك فإن أي عقد قبل عملية مغلقة عند إنهاء العملية (في نظام التشغيل Windows على أي حال).

ولكن ملاحظة ما يلي بعض من CreateMutex() المستندات:

إذا كنت تستخدم اسمه مزامنة للحد من التطبيق الخاص بك إلى مثيل واحد يمكن لمستخدم ضار إنشاء هذا مزامنة قبل أن تفعل ومنع التطبيق الخاص بك من البداية.

نعم، انها آمنة، كنت تشير الى النمط التالي، لأنك بحاجة للتأكد من أن Mutex يحصل سراح دائما.

using( Mutex mutex = new Mutex( false, "mutex name" ) )
{
    if( !mutex.WaitOne( 0, true ) )
    {
        MessageBox.Show("Unable to run multiple instances of this program.",
                        "Error",  
                        MessageBoxButtons.OK, 
                        MessageBoxIcon.Error);
    }
    else
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainForm());                  
    }
}

وهنا مقتطف شفرة

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

إذا كنت ترغب في استخدام النهج القائم على مزامنة، يجب عليك حقا استخدام مزامنة محلية ل<لأ href = "http://www.yoda.arachsys.com/csharp/faq/#one.application.instance" يختلط = "نوفولو noreferrer"> تقييد نهج فقط للمستخدم من curent في جلسة تسجيل الدخول . ونلاحظ أيضا التحذير الآخر المهم في هذا الرابط حول التخلص الموارد قوي مع اقتراب مزامنة.

والتحذير واحدة هو أن النهج القائم على مزامنة لا يسمح لك لتنشيط المقام الأول من التطبيق عندما يحاول مستخدم لبدء الدرجة الثانية.

وبديل ذلك هو PInvoke إلى FindWindow تليها SetForegroundWindow في المقام الأول. ثمة بديل آخر هو للتحقق من عملية حسب الاسم:

Process[] processes = Process.GetProcessesByName("MyApp");
if (processes.Length != 1)
{
    return;
} 

وكل من هذه البدائل الأخيرة لديك حالة سباق افتراضية حيث يمكن أن تبدأ مثيلين من التطبيق في وقت واحد ثم تكتشف بعضها البعض. هذا من غير المرجح أن يحدث في الواقع - في الواقع، خلال اختبار لم أتمكن من تحقيق ذلك.

وثمة مسألة أخرى مع هذه البدائل الأخيرتين هو أنها لن تعمل عند استخدام الخدمات الطرفية.

استخدم التطبيق مع مهلة وإعدادات الأمان مع تجنب AbandonedMutexException. كنت لي فئة مخصصة:

private class SingleAppMutexControl : IDisposable
    {
        private readonly Mutex _mutex;
        private readonly bool _hasHandle;

        public SingleAppMutexControl(string appGuid, int waitmillisecondsTimeout = 5000)
        {
            bool createdNew;
            var allowEveryoneRule = new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
                MutexRights.FullControl, AccessControlType.Allow);
            var securitySettings = new MutexSecurity();
            securitySettings.AddAccessRule(allowEveryoneRule);
            _mutex = new Mutex(false, "Global\\" + appGuid, out createdNew, securitySettings);
            _hasHandle = false;
            try
            {
                _hasHandle = _mutex.WaitOne(waitmillisecondsTimeout, false);
                if (_hasHandle == false)
                    throw new System.TimeoutException();
            }
            catch (AbandonedMutexException)
            {
                _hasHandle = true;
            }
        }

        public void Dispose()
        {
            if (_mutex != null)
            {
                if (_hasHandle)
                    _mutex.ReleaseMutex();
                _mutex.Dispose();
            }
        }
    }

واستخدامه:

    private static void Main(string[] args)
    {
        try
        {
            const string appguid = "{xxxxxxxx-xxxxxxxx}";
            using (new SingleAppMutexControl(appguid))
            {
                //run main app
                Console.ReadLine();
            }
        }
        catch (System.TimeoutException)
        {
            Log.Warn("Application already runned");
        }
        catch (Exception ex)
        {
            Log.Fatal(ex, "Fatal Error on running");
        }
    }

وهنا هو كيف وقد اتصلت هذه

في الطبقة البرنامج: 1. الحصول على System.Diagnostics.Process التطبيق الخاص بك باستخدام Process.GetCurrentProcess () 2. الخطوة من خلال مجموعة من العمليات المفتوحة مع الاسم الحالي من التطبيق الخاص بك باستخدام Process.GetProcessesByName (thisProcess.ProcessName) 3. تحقق كل process.Id ضد thisProcess.Id وإذا تم فتح مثيل بالفعل ثم لا يقل عن 1 سوف تطابق الاسم ولكن لا هوية، لا تزال على خلاف ذلك فتح المثال

using System.Diagnostics;

.....    

static void Main()
{
   Process thisProcess = Process.GetCurrentProcess();
   foreach(Process p in Process.GetProcessesByName(thisProcess.ProcessName))
   {
      if(p.Id != thisProcess.Id)
      {
         // Do whatever u want here to alert user to multiple instance
         return;
      }
   }
   // Continue on with opening application

لمسة لطيفة لإنهاء هذا الخروج سيكون لتقديم فتحت بالفعل مثلا للمستخدم، وهناك احتمالات أنهم لا يعرفون أنه كان مفتوحا، لذلك دعونا نظهر لهم ما كان عليه. للقيام بذلك يمكنني استخدام User32.dll إلى بث رسالة إلى الرسائل حلقة ويندوز، رسالة مخصصة، ولدي بلدي التطبيق الاستماع لها في أسلوب WndProc، وإذا كان يحصل على هذه الرسالة، ويعرض نفسه للمستخدم، نموذج . تظهر () أو هتنوت

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top