[C#] ستاتاسكشدولر و ستا موضوع الرسالة ضخ


Answers

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

الرمز الذي يتحدث عنه، وأضاف في .NET 2.0، موجود في وظيفة كلر الداخلية المسماة مسغويثيلبر (). شفرة المصدر ل. نيت 2.0 متاح من خلال توزيع SSCLI20. كاملة جدا، ولكن لم يتم تضمين مصدر مسغويثيلبر (). جد، غير عادي. التفكيك هو بدلا من ذلك قضية ضائعة، أنها كبيرة جدا.

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

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

user32.dll!PeekMessageW()   Unknown
combase.dll!CCliModalLoop::MyPeekMessage(tagMSG * pMsg, HWND__ * hwnd, unsigned int min, unsigned int max, unsigned short wFlag) Line 2305  C++
combase.dll!CCliModalLoop::PeekRPCAndDDEMessage() Line 2008 C++
combase.dll!CCliModalLoop::FindMessage(unsigned long dwStatus) Line 2087    C++
combase.dll!CCliModalLoop::HandleWakeForMsg() Line 1707 C++
combase.dll!CCliModalLoop::BlockFn(void * * ahEvent, unsigned long cEvents, unsigned long * lpdwSignaled) Line 1645 C++
combase.dll!ClassicSTAThreadWaitForHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * pdwIndex) Line 46 C++
combase.dll!CoWaitForMultipleHandles(unsigned long dwFlags, unsigned long dwTimeout, unsigned long cHandles, void * * pHandles, unsigned long * lpdwindex) Line 120 C++
clr.dll!MsgWaitHelper(int,void * *,int,unsigned long,int)   Unknown
clr.dll!Thread::DoAppropriateWaitWorker(int,void * *,int,unsigned long,enum WaitMode)   Unknown
clr.dll!Thread::DoAppropriateWait(int,void * *,int,unsigned long,enum WaitMode,struct PendingSync *)    Unknown
clr.dll!CLREventBase::WaitEx(unsigned long,enum WaitMode,struct PendingSync *)  Unknown
clr.dll!CLREventBase::Wait(unsigned long,int,struct PendingSync *)  Unknown
clr.dll!Thread::Block(int,struct PendingSync *) Unknown
clr.dll!SyncBlock::Wait(int,int)    Unknown
clr.dll!ObjectNative::WaitTimeout(bool,int,class Object *)  Unknown

حذار أن سجلت هذا التتبع كومة على ويندوز 8.1، وسوف تبدو مختلفة جدا على الإصدارات القديمة ويندوز. حلقة مشروط كوم تم تينكريد بشكل كبير مع في ويندوز 8، بل هو أيضا صفقة كبيرة جدا لبرامج وينرت. لا أعرف الكثير عن ذلك، ولكن يبدو أن هناك نموذج آخر خيوط ستا اسمه أستا أن تفعل نوع أكثر تقييدا ​​من الضخ، المنصوص عليها في كوايتفورمولتيوبليجكتس المضافة ()

أوبجكتناتيف :: ويتيموت () حيث يبدأ SemaphoreSlim.Wait () داخل طريقة BlockingCollection.Take () تنفيذ رمز كلر. ترى ذلك الحرث من خلال مستويات رمز كلر الداخلي للوصول إلى مسغويثيلبر الأسطوري وظيفة ()، ثم التحول إلى كوم مشهورة حلقة المرسل مشروط.

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

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

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

class MySynchronizationProvider : System.Threading.SynchronizationContext {
    public MySynchronizationProvider() {
        base.SetWaitNotificationRequired();
    }
    public override int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) {
        for (; ; ) {
            int result = MsgWaitForMultipleObjects(waitHandles.Length, waitHandles, waitAll, millisecondsTimeout, 8);
            if (result == waitHandles.Length) System.Windows.Forms.Application.DoEvents();
            else return result;
        }
    }
    [DllImport("user32.dll")]
    private static extern int MsgWaitForMultipleObjects(int cnt, IntPtr[] waitHandles, bool waitAll,
        int millisecondTimeout, int mask);        
}

وتثبيته في بداية الموضوع الخاص بك:

    System.ComponentModel.AsyncOperationManager.SynchronizationContext =
        new MySynchronizationProvider();

سترى الآن رسالة WM_TEST التي يتم إرسالها. هو دعوة إلى Application.DoEvents () التي أرسلت ذلك. أنا يمكن أن يكون قد غطت عنه باستخدام بيكمسيج + ديسباتسمسيج ولكن من شأنها أن تشوش خطر هذا الرمز، وأفضل عدم عصا دويفنتس () تحت الطاولة. كنت حقا تلعب لعبة إعادة دخول خطيرة جدا هنا. لا تستخدم هذا الرمز.

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

Question

تل؛ در: حالة توقف تام داخل مهمة يتم تشغيلها بواسطة StaTaskScheduler . نسخة طويلة:

أنا باستخدام StaTaskScheduler من StaTaskScheduler بواسطة فريق مواز، لاستضافة بعض كائنات كوم ستا القديمة الموردة من قبل طرف ثالث. وصف تفاصيل تنفيذ StaTaskScheduler يقول ما يلي:

والخبر السار هو أن تنفيذ تبل قادر على تشغيل إما على المواضيع متا أو ستا، ويأخذ في الاعتبار الاختلافات ذات الصلة حول واجهات برمجة التطبيقات الأساسية مثل WaitHandle.WaitAll (الذي يدعم فقط المواضيع متا عندما يتم توفير طريقة متعددة مقابض الانتظار).

اعتقدت أن هذا يعني أن حظر أجزاء من تبل سوف تستخدم أبي الانتظار الذي يضخ الرسائل، مثل CoWaitForMultipleHandles ، لتجنب حالات الجمود عندما دعا على مؤشر الترابط ستا.

في وضعي، وأعتقد أن ما يلي يحدث: في-بروك ستا كوم الكائن A يجعل مكالمة إلى خارج بروك كائن ب، ثم يتوقع رد من B عن طريق كجزء من المكالمة الصادرة.

في شكل مبسط:

var result = await Task.Factory.StartNew(() =>
{
    // in-proc object A
    var a = new A(); 
    // out-of-proc object B
    var b = new B(); 
    // A calls B and B calls back A during the Method call
    return a.Method(b);     
}, CancellationToken.None, TaskCreationOptions.None, staTaskScheduler);

المشكلة هي a.Method(b) لا يعود أبدا. بقدر ما أستطيع أن أقول، يحدث هذا لأن حظر الانتظار في مكان ما داخل BlockingCollection<Task> لا ضخ الرسائل، لذلك افتراضي حول البيان المذكور هو على الارجح خطأ.

إديتد يعمل نفس التعليمة البرمجية عندما يتم تنفيذها على مؤشر ترابط واجهة المستخدم لتطبيق TaskScheduler.FromCurrentSynchronizationContext() اختبار (أي توفير TaskScheduler.FromCurrentSynchronizationContext() بدلا من staTaskScheduler إلى Task.Factory.StartNew ).

ما هي الطريقة الصحيحة لحل هذه المشكلة؟ هل يجب تطبيق سياق مزامنة مخصص، والذي من شأنه أن يضخ الرسائل بشكل صريح مع CoWaitForMultipleHandles ، وتثبيته على كل مؤشر ترابط ستا الذي بدأته StaTaskScheduler ؟

إذا كان الأمر كذلك، سيتم تنفيذ BlockingCollection الأساسية استدعاء الأسلوب SynchronizationContext.Wait بي؟ هل يمكنني استخدام SynchronizationContext.WaitHelper لتنفيذ SynchronizationContext.Wait ؟

إديتد مع بعض التعليمات البرمجية تبين أن مؤشر ترابط ستا المدارة لا ضخ عند القيام حظر الانتظار. الرمز هو التطبيق وحدة التحكم كاملة جاهزة للنسخ / لصق / تشغيل:

using System;
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleTestApp
{
    class Program
    {
        // start and run an STA thread
        static void RunStaThread(bool pump)
        {
            // test a blocking wait with BlockingCollection.Take
            var tasks = new BlockingCollection<Task>();

            var thread = new Thread(() => 
            {
                // Create a simple Win32 window 
                var hwndStatic = NativeMethods.CreateWindowEx(0, "Static", String.Empty, NativeMethods.WS_POPUP,
                    0, 0, 0, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

                // subclass it with a custom WndProc
                IntPtr prevWndProc = IntPtr.Zero;

                var newWndProc = new NativeMethods.WndProc((hwnd, msg, wParam, lParam) =>
                {
                    if (msg == NativeMethods.WM_TEST)
                        Console.WriteLine("WM_TEST processed");
                    return NativeMethods.CallWindowProc(prevWndProc, hwnd, msg, wParam, lParam);
                });

                prevWndProc = NativeMethods.SetWindowLong(hwndStatic, NativeMethods.GWL_WNDPROC, newWndProc);
                if (prevWndProc == IntPtr.Zero)
                    throw new ApplicationException();

                // post a test WM_TEST message to it
                NativeMethods.PostMessage(hwndStatic, NativeMethods.WM_TEST, IntPtr.Zero, IntPtr.Zero);

                // BlockingCollection blocks without pumping, NativeMethods.WM_TEST never arrives
                try { var task = tasks.Take(); }
                catch (Exception e) { Console.WriteLine(e.Message); }

                if (pump)
                {
                    // NativeMethods.WM_TEST will arrive, because Win32 MessageBox pumps
                    Console.WriteLine("Now start pumping...");
                    NativeMethods.MessageBox(IntPtr.Zero, "Pumping messages, press OK to stop...", String.Empty, 0);
                }
            });

            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();

            Thread.Sleep(2000);

            // this causes the STA thread to end
            tasks.CompleteAdding(); 

            thread.Join();
        }

        static void Main(string[] args)
        {
            Console.WriteLine("Testing without pumping...");
            RunStaThread(false);

            Console.WriteLine("\nTest with pumping...");
            RunStaThread(true);

            Console.WriteLine("Press Enter to exit");
            Console.ReadLine();
        }
    }

    // Interop
    static class NativeMethods
    {
        [DllImport("user32")]
        public static extern IntPtr SetWindowLong(IntPtr hwnd, int nIndex, WndProc newProc);

        [DllImport("user32")]
        public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hwnd, int msg, int wParam, int lParam);

        [DllImport("user32.dll")]
        public static extern IntPtr CreateWindowEx(int dwExStyle, string lpClassName, string lpWindowName, int dwStyle, int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, IntPtr hInstance, IntPtr lpParam);

        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hwnd, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll")]
        public static extern int MessageBox(IntPtr hwnd, string text, String caption, int options);

        public delegate IntPtr WndProc(IntPtr hwnd, int msg, int wParam, int lParam);

        public const int GWL_WNDPROC = -4;
        public const int WS_POPUP = unchecked((int)0x80000000);
        public const int WM_USER = 0x0400;

        public const int WM_TEST = WM_USER + 1;
    }
}

وهذا ينتج الإخراج:

Testing without pumping...
The collection argument is empty and has been marked as complete with regards to additions.

Test with pumping...
The collection argument is empty and has been marked as complete with regards to additions.
Now start pumping...
WM_TEST processed
Press Enter to exit