programing tip

크로스 스레드 이벤트를 호출하는 가장 깨끗한 방법

itbloger 2020. 10. 15. 07:34
반응형

크로스 스레드 이벤트를 호출하는 가장 깨끗한 방법


.NET 이벤트 모델은 종종 한 스레드에서 이벤트를 발생시키고 다른 스레드에서 수신 대기하는 것과 같습니다. 백그라운드 스레드에서 내 UI 스레드로 이벤트를 마샬링하는 가장 깨끗한 방법이 무엇인지 궁금합니다.

커뮤니티 제안에 따라 다음을 사용했습니다.

// 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 루트로가는 것입니다. 몇 가지 측면을 만들고 필요한 속성을 추가하면 스레드 선호도를 다시 확인할 필요가 없습니다.


결과를 UI 스레드로 보내려면 동기화 컨텍스트를 사용하십시오. 스레드 우선 순위를 변경해야했기 때문에 스레드 풀 스레드 (코드 주석 처리) 사용에서 변경하고 내 자신의 새 스레드를 만들었습니다. 여전히 동기화 컨텍스트를 사용하여 데이터베이스 취소 성공 여부를 반환 할 수있었습니다.

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

        }
    }

I've always wondered how costly it is to always assume that invoke is required...

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

As an interesting side note, WPF's binding handles marshaling automatically so you can bind the UI to object properties that are modified on background threads without having to do anything special. This has proven to be a great timesaver for me.

In XAML:

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

You can try to develop some sort of a generic component that accepts a SynchronizationContext as input and uses it to invoke the events.

참고URL : https://stackoverflow.com/questions/22356/cleanest-way-to-invoke-cross-thread-events

반응형