سؤال

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

بناءً على اقتراحات المجتمع، استخدمت هذا:

// earlier in the code
mCoolObject.CoolEvent+= 
           new CoolObjectEventHandler(mCoolObject_CoolEvent);
// then
private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        CoolObjectEventHandler cb =
            new CoolObjectEventHandler(
                mCoolObject_CoolEvent);
        Invoke(cb, new object[] { sender, args });
        return;
    }
    // do the dirty work of my method here
}
هل كانت مفيدة؟

المحلول

بعض الملاحظات:

  • لا تقم بإنشاء مفوضين بسيطين بشكل صريح في كود كهذا إلا إذا كنت ما قبل 2.0 حتى تتمكن من استخدام:
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • لا تحتاج أيضًا إلى إنشاء مصفوفة الكائنات وتعبئتها لأن المعلمة args هي نوع "params" لذا يمكنك فقط المرور في القائمة.

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

نصائح أخرى

أملك بعض التعليمات البرمجية لهذا متصل.إنها أجمل بكثير من الاقتراحات الأخرى؛التحقق من ذلك بالتأكيد.

استخدام العينة:

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    // You could use "() =>" in place of "delegate"; it's a style choice.
    this.Invoke(delegate
    {
        // Do the dirty work of my method here.
    });
}

أنا أتجنب تصريحات المندوب الزائدة عن الحاجة.

private void mCoolObject_CoolEvent(object sender, CoolObjectEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
        return;
    }
    // do the dirty work of my method here
}

لغير الأحداث، يمكنك استخدام System.Windows.Forms.MethodInvoker مندوب أو System.Action.

يحرر:بالإضافة إلى ذلك، كل حدث له ما يقابله EventHandler تفويض لذلك ليست هناك حاجة على الإطلاق لإعادة تعريف واحد.

لقد قمت بإنشاء فئة الاتصال المتقاطعة "العالمية" التالية لغرضي الخاص، ولكن أعتقد أنه من المفيد مشاركتها:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

namespace CrossThreadCalls
{
  public static class clsCrossThreadCalls
  {
    private delegate void SetAnyPropertyCallBack(Control c, string Property, object Value);
    public static void SetAnyProperty(Control c, string Property, object Value)
    {
      if (c.GetType().GetProperty(Property) != null)
      {
        //The given property exists
        if (c.InvokeRequired)
        {
          SetAnyPropertyCallBack d = new SetAnyPropertyCallBack(SetAnyProperty);
          c.BeginInvoke(d, c, Property, Value);
        }
        else
        {
          c.GetType().GetProperty(Property).SetValue(c, Value, null);
        }
      }
    }

    private delegate void SetTextPropertyCallBack(Control c, string Value);
    public static void SetTextProperty(Control c, string Value)
    {
      if (c.InvokeRequired)
      {
        SetTextPropertyCallBack d = new SetTextPropertyCallBack(SetTextProperty);
        c.BeginInvoke(d, c, Value);
      }
      else
      {
        c.Text = Value;
      }
    }
  }

ويمكنك ببساطة استخدام SetAnyProperty() من موضوع آخر:

CrossThreadCalls.clsCrossThreadCalls.SetAnyProperty(lb_Speed, "Text", KvaserCanReader.GetSpeed.ToString());

في هذا المثال، تقوم فئة KvaserCanReader المذكورة أعلاه بتشغيل مؤشر الترابط الخاص بها وإجراء استدعاء لتعيين خاصية النص الخاصة بالتسمية lb_Speed ​​في النموذج الرئيسي.

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

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

    #region SyncContextCancel

    private SynchronizationContext _syncContextCancel;

    /// <summary>
    /// Gets the synchronization context used for UI-related operations.
    /// </summary>
    /// <value>The synchronization context.</value>
    protected SynchronizationContext SyncContextCancel
    {
        get { return _syncContextCancel; }
    }

    #endregion //SyncContextCancel

    public void CancelCurrentDbCommand()
    {
        _syncContextCancel = SynchronizationContext.Current;

        //ThreadPool.QueueUserWorkItem(CancelWork, null);

        Thread worker = new Thread(new ThreadStart(CancelWork));
        worker.Priority = ThreadPriority.Highest;
        worker.Start();
    }

    SQLiteConnection _connection;
    private void CancelWork()//object state
    {
        bool success = false;

        try
        {
            if (_connection != null)
            {
                log.Debug("call cancel");
                _connection.Cancel();
                log.Debug("cancel complete");
                _connection.Close();
                log.Debug("close complete");
                success = true;
                log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());
            }
        }
        catch (Exception ex)
        {
            log.Error(ex.Message, ex);
        }

        SyncContextCancel.Send(CancelCompleted, new object[] { success });
    }

    public void CancelCompleted(object state)
    {
        object[] args = (object[])state;
        bool success = (bool)args[0];

        if (success)
        {
            log.Debug("long running query cancelled" + DateTime.Now.ToLongTimeString());

        }
    }

لقد تساءلت دائمًا عن مدى تكلفة ذلك دائماً لنفترض أن الاستدعاء مطلوب ...

private void OnCoolEvent(CoolObjectEventArgs e)
{
  BeginInvoke((o,e) => /*do work here*/,this, e);
}

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

في XAML:

<TextBox Text="{Binding Path=Name}"/>

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

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