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


79

.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
}

기존 관리되는 컨트롤에 아직 관리되지 않는 핸들이없는 경우 InvokeRequired가 false를 반환 할 수 있습니다. 제어가 완전히 생성되기 전에 발생할 이벤트에주의를 기울여야합니다.
GregC

답변:


28

몇 가지 관찰 :

  • 2.0 이전 버전이 아니라면 다음과 같이 코드에서 명시 적으로 간단한 대리자를 만들지 마십시오.
   BeginInvoke(new EventHandler<CoolObjectEventArgs>(mCoolObject_CoolEvent), 
               sender, 
               args);
  • 또한 args 매개 변수가 "params"유형이므로 목록을 전달하기 만하면되기 때문에 객체 배열을 만들고 채울 필요가 없습니다.

  • 아마 선호하는 것 Invoke이상 BeginInvoke코드에서 후자의 의지의 결과 또는 당신이 계신하지 않을 수도 있지만를 호출하지 않고 전파하기 어려운 이후의 예외를 처리 할 것이다 비동기 적으로 호출되는 것으로 EndInvoke. 무슨 일이 벌어 질지는 당신의 앱이 결국 TargetInvocationException대신 받게 될 것입니다.


44

나는이 이것에 대한 몇 가지 코드를 온라인으로. 다른 제안보다 훨씬 좋습니다. 확실히 확인하십시오.

샘플 사용법 :

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

System.Windows.Forms확장에서 네임 스페이스를 로 변경할 수도 있습니다 . 이렇게 하면 필요할 때마다 사용자 지정 네임 스페이스를 추가하지 않아도됩니다.
Joe Almore

10

중복 된 델리게이트 선언을 피합니다.

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델리게이트가 있으므로 다시 선언 할 필요가 없습니다.


1
: 나에게는이 방법 일Invoke(new Action<object, CoolObjectEventArgs>(mCoolObject_CoolEvent), sender, args);
안토니오 알메이다

@ToniAlmeida 예, 내 코드의 오타였습니다. 지적 해 주셔서 감사합니다.
Konrad Rudolph

4

내 목적을 위해 다음과 같은 '유니버설'크로스 스레드 호출 클래스를 만들었지 만 공유 할 가치가 있다고 생각합니다.

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 ​​레이블의 텍스트 속성을 설정하기 위해 호출합니다.


3

가장 깨끗한 방법은 확실히 AOP 루트로가는 것입니다. 몇 가지 측면을 만들고 필요한 속성을 추가하면 스레드 선호도를 다시 확인할 필요가 없습니다.


나는 당신의 제안을 이해하지 못합니다. C #은 기본적으로 측면 지향 언어가 아닙니다. 이면에서 마샬링을 구현하는 측면을 구현하기 위해 패턴이나 라이브러리를 염두에두고 있습니까?
Eric

PostSharp를 사용하므로 속성 클래스에서 스레딩 동작을 정의한 다음 UI 스레드에서 호출해야하는 모든 메서드 앞에 [WpfThread] 속성을 사용합니다.
Dmitri Nesteruk

3

결과를 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());

        }
    }

2

나는 항상하는 것이 얼마나 비용이 많이 드는 궁금했습니다 항상 호출이 필요한 가정 ...

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

1
GUI 스레드 내에서 BeginInvoke를 수행하면 다음에 UI 스레드가 Windows 메시지를 처리 ​​할 때까지 해당 작업이 지연됩니다. 이것은 실제로 어떤 경우에 유용한 일이 될 수 있습니다.
supercat 2011-04-17

2

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

XAML에서 :

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

이것은 작동하지 않습니다. 비 UI 스레드에 소품을 설정하면 예외가 발생합니다. 즉 Name = "gbc"bang! 실패 ... 공짜 치즈 메이트는 없습니다
Boppity Bop

무료는 아니지만 (실행 시간이 소요됨) wpf 바인딩 기계가 크로스 스레드 마샬링을 자동으로 처리하는 것처럼 보입니다. 우리는 백그라운드 스레드에서 수신 된 네트워크 데이터에 의해 업데이트되는 props와 함께 이것을 많이 사용합니다. 여기에 설명이 있습니다 : blog.lab49.com/archives/1166
gbc 2011

1
@gbc Aaaaand 설명은 404 사라
월 'splite'K.에게

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.