문제

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

        }
    }

항상 비용이 얼마나 드는지 궁금했는데 언제나 호출이 필요하다고 가정합니다 ...

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

흥미로운 점은 WPF의 바인딩이 자동으로 마샬링을 처리하므로 특별한 작업을 수행하지 않고도 백그라운드 스레드에서 수정되는 개체 속성에 UI를 바인딩할 수 있다는 것입니다.이것은 나에게 큰 시간 절약 효과가 있음이 입증되었습니다.

XAML에서:

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

다음을 허용하는 일종의 일반 구성 요소를 개발해 볼 수 있습니다. 동기화컨텍스트 입력으로 사용하고 이를 사용하여 이벤트를 호출합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top