2009-12-10 5 views
6

나는 datagridview에 바인딩 된INotifyPropertyChanged with threads

 BindingList<T> 

을 가지고 있습니다. 내 클래스의 한 속성은 계산 시간이 오래 걸리므로 작업을 스레드했습니다. 계산이 끝나면 값이 준비되었음을 알리는 OnPropertyChanged() 이벤트를 발생시킵니다.

적어도 그것이 이론입니다. 그러나 OnPropertyChanged 메서드는 differend 스레드에서 호출되기 때문에 눈금의 OnRowPrePaint 메서드에서 예외가 발생합니다.

누구나 내가 OnPropertyChanged 이벤트를 메인 스레드에서 실행되도록 미리 알려줄 수 있습니까? MyClass 클래스는 Winforms 응용 프로그램에서 실행된다는 것을 알지 못하기 때문에 Form.Invoke를 사용할 수 없습니다.

public class MyClass : INotifyPropertyChanged 
{ 
    public int FastMember {get;set;} 

    private int? slowMember; 
    public SlowMember 
    { 
     get 
     { 
      if (slowMember.HasValue) 
       return slowMember.Value; 
      else 
      { 
       Thread t = new Thread(getSlowMember); 
       t.Start(); 
       return -1; 
      } 

     } 
    } 

    private void getSlowMember() 
    { 
     Thread.Sleep(1000); 
     slowMember = 5; 
     OnPropertyChanged("SlowMember"); 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
    private void OnPropertyChanged(string propertyName) 
    { 
     PropertyChangingEventHandler eh = PropertyChanging; 
     if (eh != null) 
     { 
      eh(this, e); 
     } 
    } 

} 

답변

8

의도적으로 컨트롤은 생성 된 스레드에서만 업데이트 할 수 있습니다. 따라서 예외가 발생합니다.

BackgroundWorker을 사용하고 이벤트 처리기를 RunWorkerCompleted에 구독하여 오래 지속되는 작업 만 완료 한 후에 회원을 업데이트하는 것이 좋습니다.

+0

매력처럼 작동합니다. 지금까지 나는 BackgroundWorker에 대해 몰랐다. 이렇게하면이 작업이 매우 쉽습니다. –

1

고려 사항 1 :
이 문서에서 UIThreadMarshal 클래스에서 모양과 그것의 사용을 가지고 :
UI Thread Marshaling in the Model Layer
당신은 인스턴스에 정적에서 클래스를 변경하고 객체에 주입 할 수 있습니다. 그래서 당신의 객체는 Form 클래스에 대해 알지 못할 것입니다. UIThreadMarshal 클래스에 대해서만 알 수 있습니다.

고려 사항 2 :
재산에서 -1을 반환하는 것이 좋습니다. 나에게 나쁜 디자인처럼 보입니다.

고려 사항 3 :
어쩌면 수업에 정지 스레드를 사용하지 않아야합니다. 어쩌면 귀하의 재산을 직접 호출하거나 별도의 스레드로 호출하는 방법을 결정해야하는 소비자 클래스 일 수 있습니다. 이 경우 IsSlowMemberInitialized와 같은 추가 속성을 제공해야 할 수도 있습니다.

+0

하려면 1 : 링크를 가져 주셔서 감사합니다. 이 경우 BackgroundWorker가 내 문제를 해결했지만 가까운 장래에 반바지가 필요할 것입니다. 2 : 맞습니다. 특히 SlowMember가 -1 일 수 있습니다. 3으로 : DataGridView가 값을 쿼리하고 (값을 업데이트하고 INotifyPropertyChanged 인터페이스를 사용하여 datagridview에 변경된 속성을 알리는 것보다 처음으로 -1을 얻음) 가능하지 않습니다. (타이머를 사용하고 IsSlowMemberInitialized = true를 검사 할 수 있지만 추한 것입니다.) 어쨌든 많이 쓰다 –

+0

DataGridView를 사용한다면 BindingSource를 사용해야 할 것입니다. 내가 준 링크에서, 다른 스레드에서 바인딩을 지원하는 BindingSource 구현이 있습니다.이 코드를 사용하여 사용자의 요구에 더 잘 맞출 수 있습니다. – nightcoder

2

다음은 내가 전에 쓴 것입니다. 이

using System.ComponentModel; 
using System.Threading; 
public class ThreadedBindingList<T> : BindingList<T> { 
    SynchronizationContext ctx = SynchronizationContext.Current; 
    protected override void OnAddingNew(AddingNewEventArgs e) { 
     if (ctx == null) { BaseAddingNew(e); } 
     else { ctx.Send(delegate { BaseAddingNew(e); }, null); } 
    } 
    protected override void OnListChanged(ListChangedEventArgs e) { 
     if (ctx == null) { BaseListChanged(e); } 
     else { ctx.Send(delegate { BaseListChanged(e); }, null); } 
    } 
    void BaseListChanged(ListChangedEventArgs e) { base.OnListChanged(e); } 
    void BaseAddingNew(AddingNewEventArgs e) { base.OnAddingNew(e); } 
} 
+0

흥미로운 구현 Marc이지만 나쁜 설계를 허용하므로 특정 시나리오에서만 사용해야합니다. 작업이 처리되는 동안 실제로 컨트롤을 업데이트해야합니다. –

6

사람들은 때때로 같은 이벤트 핸들러가 MultiCastDelegate 것을 잊고 ... 잘 합리적으로을 을 작동하지만 업데이트 많은 비용을주의해야한다, 각 가입자에 대한 모든 정보를 가지고 그 우리 불필요하게 Invoke + Synchronization 성능 페널티를 부과하지 않으면 서 정상적으로이 상황을 처리해야합니다. 그것은 무엇을

using System.ComponentModel; 
// ... 

public event PropertyChangedEventHandler PropertyChanged; 

protected virtual void OnPropertyChanged(string propertyName) 
{ 
    var handler = PropertyChanged; 
    if (handler != null) 
    { 
     var e = new PropertyChangedEventArgs(propertyName); 
     foreach (EventHandler h in handler.GetInvocationList()) 
     { 
      var synch = h.Target as ISynchronizeInvoke; 
      if (synch != null && synch.InvokeRequired) 
       synch.Invoke(h, new object[] { this, e }); 
      else 
       h(this, e); 
     } 
    } 
} 

은 간단하지만, 나는 거의 그것을 할 수있는 최선의 방법을 찾기 위해 노력하고 다시 내 머리에 금이 기억 : 나는 나이를 위해 다음과 같은 코드를 사용하고있다.

먼저 경쟁 속성을 피하기 위해 로컬 속성에서 이벤트 처리기를 "가져옵니다".

핸들러가 null이 아니거나 (하나의 구독자가 존재하는 경우) 이벤트 args를 준비한 다음이 멀티 캐스트 대리자의 호출 목록을 반복합니다.

호출 목록에는 대상 등록 정보 인 이벤트 등록자가 있습니다.이 구독자가 ISynchronizeInvoke (모든 UI 컨트롤이 구현 함)를 구현하면 InvokeRequired 속성을 확인하고 대리자 및 매개 변수를 전달하는 호출을 수행합니다. 이 방법으로 호출하면 호출이 UI 스레드로 동기화됩니다.

그렇지 않으면 간단히 이벤트 핸들러를 직접 호출합니다.

+2

'System.InvalidCastException' 정보가'{ "'System.EventMandel.PropertyChangedEventHandler '유형의 객체를'System.EventHandler '유형으로 캐스팅 할 수 없으므로'EventChanded'를'PropertyChangedEventHandler'로 이름을 바꾸어야했습니다." }' 내부적으로 이벤트를 구독하는 UI 스레드에서 만든 BindingList가 있지만 h.Target이 null이기 때문에 동기화 변수는 항상 null을 반환합니다. –

+0

@RickShealer와 동일한 문제가 발생합니다. 이것이 최신 버전의 .Net에 문제가 있는지 궁금해하는 날짜를 지적하십니까? 이것은 크로스 스레드 INotifyPropertyChanged 문제에 대한 매우 우아한 해결책 인 것 같습니다. 그래서 우리는 그것을 얻을 수 있기를 바랍니다. – Jacob

+0

@Jacob 실패한 지 확인하기 위해 새로운 프레임 워크를 대상으로하겠습니다. 프로젝트의 프레임 워크 대상 버전이나 기타 관련 있다고 생각되는 정보를 말해 줄 수 있습니까? – Loudenvier