هل هناك طريقة جيدة في C# لطرح استثناء على مؤشر ترابط معين

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

  •  09-06-2019
  •  | 
  •  

سؤال

الكود الذي أريد كتابته هو كالتالي:

void MethodOnThreadA()
{
    for (;;)
    {
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    }
}

void MethodOnThreadB()
{
    try
    {
        for (;;)
        {
            // Do stuff
        }
    }
    catch (MyException ex)
    {
        // Do the right thing for this exception.
    }
}

أعلم أنه يمكنني إجراء فحص دوري للخيط B، بطريقة آمنة للخيط، لمعرفة ما إذا كان قد تم تعيين علامة بواسطة الخيط A، ولكن هذا يجعل الكود أكثر تعقيدًا.هل هناك آلية أفضل يمكنني استخدامها؟

فيما يلي مثال أكثر تفصيلاً للفحص الدوري:

Dictionary<Thread, Exception> exceptionDictionary = new Dictionary<Thread, Exception>();

void ThrowOnThread(Thread thread, Exception ex)
{
    // the exception passed in is going to be handed off to another thread,
    // so it needs to be thread safe.
    lock (exceptionDictionary)
    {
        exceptionDictionary[thread] = ex;
    }
}

void ExceptionCheck()
{
    lock (exceptionDictionary)
    {
        Exception ex;
        if (exceptionDictionary.TryGetValue(Thread.CurrentThread, out ex))
            throw ex;
    }
}

void MethodOnThreadA()
{
    for (;;)
    {
        // Do stuff
        if (ErrorConditionMet)
            ThrowOnThread(threadB, new MyException(...));
    }
}

void MethodOnThreadB()
{
    try
    {
        for (;;)
        {
            // Do stuff
            ExceptionCheck();
        }
    }
    catch (MyException ex)
    {
        // Do the right thing for this exception.
    }
}
هل كانت مفيدة؟

المحلول

هذه ليست فكرة جيدة

تتحدث هذه المقالة عن مكتبة مهلة روبي. الذي يلقي استثناءات عبر المواضيع.

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

باختصار، ما يمكن (ويحدث) أن يحدث هو ما يلي:

الموضوع أ:

At some random time, throw an exception on thread B:

الموضوع ب:

try {
    //do stuff
} finally {
    CloseResourceOne();
    // ThreadA's exception gets thrown NOW, in the middle 
    // of our finally block and resource two NEVER gets closed.
    // Obviously this is BAD, and the only way to stop is to NOT throw
    // exceptions across threads
    CloseResourceTwo();
}

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

نصائح أخرى

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

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

على الأرجح لن يعرف هذا الخيط الآخر كيفية التعامل مع الاستثناء في جميع الحالات التي يمكن أن يتم طرحها فيها بواسطة التعليمات البرمجية الخاصة بك.

باختصار، يجب أن تجد آلية أخرى لإجهاض سلاسل الرسائل الخاصة بك بدلاً من استخدام الاستثناءات.

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

أثناء بحثي في ​​موضوع آخر، صادفني هذا المقال الذي ذكرني بسؤالك:

السباكة في أعماق ThreadAbortException باستخدام Rotor

إنه يُظهر التقلبات التي يمر بها .NET لتنفيذ Thread.Abort() - ومن المفترض أن يكون أي استثناء آخر عبر سلاسل المحادثات مشابهًا.(نعم!)

ما يقوله أوريون إدواردز ليس صحيحًا تمامًا:ليست الطريقة "الوحيدة".

// Obviously this is BAD, and the only way to stop is to NOT throw
// exceptions across threads

استخدام خفض الانبعاثات المعتمد (مناطق التنفيذ المقيدة) في C# يسمح لك بتحرير مواردك كعملية ذرية، مما يحمي التعليمات البرمجية الخاصة بك من الاستثناءات بين الخيوط.يتم استخدام هذه التقنية بواسطة عدة فئات من .NET Framework التي تعمل مع واجهة برمجة التطبيقات الأصلية لنظام التشغيل Windows، حيث قد يتسبب المقبض الذي لم يتم إصداره في حدوث تسرب للذاكرة.

يرى http://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.runtimehelpers.prepareconstrainedregions.aspx

يوضح المثال التالي كيفية تعيين المقابض بشكل موثوق باستخدام PrepareConstrainedRegions طريقة.لتعيين مؤشر بشكل موثوق لمقبض محدد موجود مسبقًا، يجب عليك التأكد من أن تخصيص المقبض الأصلي والتسجيل اللاحق لذلك المقبض داخل SafeHandle الكائن ذري.سيؤدي أي فشل بين هذه العمليات (مثل إحباط مؤشر الترابط أو استثناء نفاد الذاكرة) إلى تسرب المقبض الأصلي.يمكنك استخدام ال PrepareConstrainedRegions طريقة للتأكد من عدم تسرب المقبض.

ببساطة:

public MySafeHandle AllocateHandle()
{
    // Allocate SafeHandle first to avoid failure later.
    MySafeHandle sh = new MySafeHandle();

    RuntimeHelpers.PrepareConstrainedRegions();
    try { }
    finally  // this finally block is atomic an uninterruptible by inter-thread exceptions
    {
        MyStruct myStruct = new MyStruct();
        NativeAllocateHandle(ref myStruct);
        sh.SetHandle(myStruct.m_outputHandle);
    }

    return sh;
}

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

لا أعتقد أن هذه فكرة جيدة..خذ حلاً آخر لهذه المشكلة - حاول استخدام آلية أخرى مثل البيانات المشتركة للإشارة بين سلاسل الرسائل.

مثل الآخرين، لست متأكدًا من أن هذه فكرة جيدة، ولكن إذا كنت تريد حقًا القيام بذلك، فيمكنك إنشاء فئة فرعية من SynchronizationContext التي تسمح بنشر المفوضين وإرسالهم إلى سلسلة الرسائل المستهدفة (إذا كانت سلسلة رسائل WinForms فإن العمل يتم ذلك من أجلك لأن هذه الفئة الفرعية موجودة بالفعل).سيتعين على مؤشر الترابط الهدف تنفيذ نوع ما من مضخة الرسائل المكافئة لاستقبال المفوضين.

@ أوريون إدواردز

أتقبل وجهة نظرك بشأن طرح استثناء في الكتلة النهائية.

ومع ذلك، أعتقد أن هناك طريقة - باستخدام موضوع آخر - لاستخدام فكرة الاستثناء كمقاطعة.

الموضوع أ:

At some random time, throw an exception on thread C:

الموضوع ب:

try {
    Signal thread C that exceptions may be thrown
    //do stuff, without needing to check exit conditions
    Signal thread C that exceptions may no longer be thrown
}
catch {
    // exception/interrupt occurred handle...
}
finally {
    // ...and clean up
    CloseResourceOne();
    CloseResourceTwo();
}

الموضوع ج:

 while(thread-B-wants-exceptions) {
        try {
            Thread.Sleep(1) 
        }
        catch {
            // exception was thrown...
            if Thread B still wants to handle exceptions
                throw-in-B
        }
    }

أم أن هذا مجرد سخيف؟

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