كيف يمكنني جعل عمليات الاسترجاعات للأحداث في سلسلة رسائل نماذج الفوز الخاصة بي آمنة؟

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

سؤال

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

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

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

المحلول

لتبسيط كود سيمون قليلاً، يمكنك استخدام مندوب الإجراء العام المدمج.فهو يوفر عليك إضافة مجموعة من أنواع المفوضين التي لا تحتاج إليها حقًا إلى التعليمات البرمجية الخاصة بك.أيضًا، في .NET 3.5 قاموا بإضافة معلمة params إلى أسلوب الاستدعاء بحيث لا يتعين عليك تحديد مصفوفة مؤقتة.

void SomethingHappened(object sender, EventArgs ea)
{
   if (InvokeRequired)
   {
      Invoke(new Action<object, EventArgs>(SomethingHappened), sender, ea);
      return;
   }

   textBox1.Text = "Something happened";
}

نصائح أخرى

وفيما يلي النقاط البارزة:

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

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

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

private delegate void EventArgsDelegate(object sender, EventArgs ea);

void SomethingHappened(object sender, EventArgs ea)
{
   //
   // Make sure this callback is on the correct thread
   //
   if (this.InvokeRequired)
   {
      this.Invoke(new EventArgsDelegate(SomethingHappened), new object[] { sender, ea });
      return;
   }

   //
   // Do something with the event such as update a control
   //
   textBox1.Text = "Something happened";
}

انها بسيطة جدا حقا.

  1. يستخدم مطلوب الاستدعاء لمعرفة ما إذا كان رد الاتصال هذا قد حدث في الموضوع الصحيح.
  2. إذا لم يكن الأمر كذلك، فقم بإعادة استدعاء رد الاتصال على مؤشر الترابط الصحيح بنفس المعلمات.يمكنك إعادة استدعاء الطريقة باستخدام يستحضر (الحظر) أو BeginInvoc طرق (عدم الحجب).
  3. في المرة التالية التي يتم فيها استدعاء الدالة، مطلوب الاستدعاء تُرجع خطأ لأننا الآن في المسار الصحيح والجميع سعداء.

هذه طريقة مدمجة للغاية لمعالجة هذه المشكلة وجعل النماذج الخاصة بك آمنة من عمليات الاسترجاعات للأحداث متعددة الخيوط.

أستخدم طرقًا مجهولة كثيرًا في هذا السيناريو:

void SomethingHappened(object sender, EventArgs ea)
{
   MethodInvoker del = delegate{ textBox1.Text = "Something happened"; }; 
   InvokeRequired ? Invoke( del ) : del(); 
}

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

فيما يلي مثال مختصر يسمح فقط باستدعاء متزامن واحد؛يتطلب دعم الدعوات/الأحداث المتعددة المزيد من السباكة.

using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public class MainForm : Form
    {
        private TypeWithAsync _type;

        [STAThread()]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.Run(new MainForm());
        }

        public MainForm()
        {
            _type = new TypeWithAsync();
            _type.DoSomethingCompleted += DoSomethingCompleted;

            var panel = new FlowLayoutPanel() { Dock = DockStyle.Fill };

            var btn = new Button() { Text = "Synchronous" };
            btn.Click += SyncClick;
            panel.Controls.Add(btn);

            btn = new Button { Text = "Asynchronous" };
            btn.Click += AsyncClick;
            panel.Controls.Add(btn);

            Controls.Add(panel);
        }

        private void SyncClick(object sender, EventArgs e)
        {
            int value = _type.DoSomething();
            MessageBox.Show(string.Format("DoSomething() returned {0}.", value));
        }

        private void AsyncClick(object sender, EventArgs e)
        {
            _type.DoSomethingAsync();
        }

        private void DoSomethingCompleted(object sender, DoSomethingCompletedEventArgs e)
        {
            MessageBox.Show(string.Format("DoSomethingAsync() returned {0}.", e.Value));
        }
    }

    class TypeWithAsync
    {
        private AsyncOperation _operation;

        // synchronous version of method
        public int DoSomething()
        {
            Thread.Sleep(5000);
            return 27;
        }

        // async version of method
        public void DoSomethingAsync()
        {
            if (_operation != null)
            {
                throw new InvalidOperationException("An async operation is already running.");
            }

            _operation = AsyncOperationManager.CreateOperation(null);
            ThreadPool.QueueUserWorkItem(DoSomethingAsyncCore);
        }

        // wrapper used by async method to call sync version of method, matches WaitCallback so it
        // can be queued by the thread pool
        private void DoSomethingAsyncCore(object state)
        {
            int returnValue = DoSomething();
            var e = new DoSomethingCompletedEventArgs(returnValue);
            _operation.PostOperationCompleted(RaiseDoSomethingCompleted, e);
        }

        // wrapper used so async method can raise the event; matches SendOrPostCallback
        private void RaiseDoSomethingCompleted(object args)
        {
            OnDoSomethingCompleted((DoSomethingCompletedEventArgs)args);
        }

        private void OnDoSomethingCompleted(DoSomethingCompletedEventArgs e)
        {
            var handler = DoSomethingCompleted;

            if (handler != null) { handler(this, e); }
        }

        public EventHandler<DoSomethingCompletedEventArgs> DoSomethingCompleted;
    }

    public class DoSomethingCompletedEventArgs : EventArgs
    {
        private int _value;

        public DoSomethingCompletedEventArgs(int value)
            : base()
        {
            _value = value;
        }

        public int Value
        {
            get { return _value; }
        }
    }
}

كما lazy programmer, ، لدي طريقة كسولة جدًا للقيام بذلك.

ما أفعله هو هذا بكل بساطة.

private void DoInvoke(MethodInvoker del) {
    if (InvokeRequired) {
        Invoke(del);
    } else {
        del();
    }
}
//example of how to call it
private void tUpdateLabel(ToolStripStatusLabel lbl, String val) {
    DoInvoke(delegate { lbl.Text = val; });
}

يمكنك تضمين DoInvocation داخل وظيفتك أو إخفائه داخل وظيفة منفصلة للقيام بالعمل القذر نيابةً عنك.

فقط ضع في اعتبارك أنه يمكنك تمرير الوظائف مباشرة إلى طريقة DoInvoc.

private void directPass() {
    DoInvoke(this.directInvoke);
}
private void directInvoke() {
    textLabel.Text = "Directly passed.";
}

في العديد من الحالات البسيطة، يمكنك استخدام مفوض MethodInvocer وتجنب الحاجة إلى إنشاء نوع المفوض الخاص بك.

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