2011-12-30 7 views
5

私は、別のオブジェクト(B)への参照を保持するオブジェクト(A)を作成しています。私のコードのUI部分は、DevExpressグリッドビューのデータソースとして使用されるBindingList内のオブジェクト(A)を保持します。コントローラは新しく作成されたオブジェクト(A)をイベントを介してUIに送信します。コントローラには、参照オブジェクト(B)を更新するスレッドもあります。スローされた例外は、DevExpress GridViewから取得され、「クロススレッド操作が検出されました。この例外を抑制するには、DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true」と設定します。参照オブジェクトをスレッド間で使用する

コードが最終的にクリティカルなアプリケーションで終了するため、この例外を抑制したくありません。

問題を起こさずにスレッド間で参照オブジェクトを更新するにはどうすればよいですか?ここで私のテストアプリケーションのコードです。実際のプログラムでは基本的に同じになります。

UPDATE UIの誤差はニコラス・バトラーからの回答で修正されましたが、現在の例外は、Employeeクラスに移動しました。変更を反映するようにコードを更新しました。

ここに私のコード

* UI *

public partial class Form1 : Form 
{ 
    private BindingList<IEmployee> empList; 
    EmployeeController controller; 
    private delegate void AddEmployeInvoke(IEmployee employee); 
    public Form1() 
    { 
     controller = new EmployeeController(); 
     controller.onNewEmployee += new EmployeeController.NewEmployee(controller_onNewEmployee); 
     empList = new BindingList<IEmployee>(); 
     InitializeComponent(); 
    } 

    void controller_onNewEmployee(IEmployee emp) 
    { 
     AddEmployee(emp); 
    } 

    private void AddEmployee(IEmployee empl) 
    { 
     if (InvokeRequired) 
     { 
      this.Invoke(new AddEmployeInvoke(AddEmployee), new Object[] {empl}); 
     } 
     else 
     { 
      empList.Add(empl); 
     } 
    } 

    private void Form1_Load(object sender, EventArgs e) 
    { 
     this.gridControl1.DataSource = empList; 
     this.gridControl1.RefreshDataSource(); 
     controller.Start(); 
    } 
} 

コントローラーです:

class EmployeeController 
{ 
    List<IEmployee> emps; 
    Task empUpdater; 
    CancellationToken cancelToken; 
    CancellationTokenSource tokenSource; 
    Pay payScale1; 
    Pay payScale2; 

    public EmployeeController() 
    { 
     payScale1 = new Pay(12.00, 10.00); 
     payScale2 = new Pay(14.00, 11.00); 
     emps = new List<IEmployee>(); 
    } 

    public void Start() 
    { 
     empUpdater = new Task(AddEmployee, cancelToken); 
     tokenSource = new CancellationTokenSource(); 
     cancelToken = tokenSource.Token; 
     empUpdater.Start(); 
    } 

    public bool Stop() 
    { 
     tokenSource.Cancel(); 
     while (!empUpdater.IsCompleted) 
     { } 
     return true; 
    } 

    private void AddEmployee() 
    { 
     IEmployee emp = new Employee("steve", ref payScale1); 
     ThrowEmployeeEvent(emp); 
     emps.Add(emp); 
     emp = new Employee("bob", ref payScale2); 
     ThrowEmployeeEvent(emp); 
     emps.Add(emp); 
     int x = 0; 

     while (!cancelToken.IsCancellationRequested) 
     { 
      emp = new Employee("Emp" + x, ref payScale1); 
      ThrowEmployeeEvent(emp); 
      x++; 
      emp = new Employee("Emp" + x, ref payScale2); 
      ThrowEmployeeEvent(emp); 

      Thread.Sleep(1000); 

      payScale2.UpdatePay(10.0); 
      payScale1.UpdatePay(11.0); 

      Thread.Sleep(5000); 
     } 
    } 

    private void ThrowEmployeeEvent(IEmployee emp) 
    { 
     if (onNewEmployee != null) 
      onNewEmployee(emp); 
    } 

    public delegate void NewEmployee(IEmployee emp); 
    public event NewEmployee onNewEmployee; 
} 

従業員クラス:(例外はこのクラスでスロー)

は、 0
class Employee : IEmployee 
{ 
    private string _name; 
    private double _salary; 
    private Pay _myPay; 
    public string Name 
    { 
     get { return _name; } 
     set { _name = value; 
      //if (PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("Name")); 
      } 
    }   
    public double Salary 
    { 
     get { return _salary; } 
    } 
    int x = 1; 

    public Employee(string name, ref Pay pay) 
    { 
     _myPay = pay; 
     _myPay.PropertyChanged += new PropertyChangedEventHandler(_myPay_PropertyChanged); 
     _salary = _myPay.Salary; 
     Name = name; 
    } 

    void _myPay_PropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     if (e.PropertyName == "Salary") 
     { 
      _salary = _myPay.Salary; 
      if (this.PropertyChanged != null) 
       // exception thrown on the line below 
       this.PropertyChanged(this, new PropertyChangedEventArgs("Salary")); 
     } 
    } 

    public void ChangeName() 
    { 
     Name = "Me " + x; 
     x++; 
    } 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

従業員インタフェース:

interface IEmployee : INotifyPropertyChanged 
{ 
    string Name { get; set; } 
    double Salary { get;} 
} 

ペイクラス:

class Pay : INotifyPropertyChanged 
{ 
    private double _salary; 
    private double _bonus; 
    public double Salary { get { return _salary; } set { _salary = value; if(PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("Salary"));} } 
    public double Bonus { get { return _bonus; } set { _bonus = value; if (PropertyChanged != null) this.PropertyChanged(this, new PropertyChangedEventArgs("Bonus")); } } 

    public Pay(double salary, double bonus) 
    { 
     Salary = salary; 
     Bonus = bonus; 
    } 

    public void UpdatePay(double salary) 
    { 
     Salary += salary; 
     if (onChange != null) 
      this.onChange(); 
    } 

    public void UpdatePay(double salary, double bonus) 
    { 
     Salary += salary; 
     Bonus += bonus; 

     if (onChange != null) 
      this.onChange(); 
    } 

    public delegate void Change(); 
    public event Change onChange; 

    public event PropertyChangedEventHandler PropertyChanged; 
} 

私は非常に任意の助けに感謝。

答えて

2

問題はEmployeeController.onNewEmployeeが非UIスレッドで解雇されていることです。イベントベースの非同期パターンを使用して、特定のスレッド(この場合はUI)のスレッド(http://msdn.microsoft.com/en-us/library/hkasytyf.aspx)を発生させます。

また、各イベントハンドラでIsInvokeRequiredを確認し、そうであれば.Invokeを使用してUIスレッドに戻ることができます。これはもっと面倒ですが、実装するのが簡単で簡単かもしれません。

1

InvokeRequired == trueの場合でもempList.Add(empl);に電話しています。試してみてください:

private void AddEmployee(IEmployee empl) 
{ 
    if (InvokeRequired) 
    { 
     this.Invoke(new AddEmployeInvoke(AddEmployee), new Object[] {empl}); 
    } 
    else 
    { 
     empList.Add(empl); //exception thrown here 
    } 
} 

ます。また、UIスレッド上であなたINotifyPropertyChangedイベントを発生する必要がありますが、あなたは上Invokeを呼び出すためのUIコントロールを持っていません。

public partial class Form1 : Form 
{ 
    public static Control UI { get; private set; } 

    public Form1() 
    { 
     UI = this; 
    } 
} 

あなたは、あなたのアプリのどこからでもForm1.UI.InvokeRequiredForm1.UI.Invokeを使用することができます。これを行う簡単な方法は、あなたのメインフォームへの参照を格納し、それをpublic staticを作ることです。


私は、一度に1つのステップを取るしようとしていたが、あなたは、より正確なソリューションをしたい場合は、コントローラにUI SynchronizationContextを渡し、そのPostまたはSendメソッドを使用することができます。

public Form1() 
{ 
    controller = new EmployeeController(SynchronizationContext.Current); 
    ... 

class EmployeeController 
{ 
    private SynchronizationContext _SynchronizationContext = null; 

    public EmployeeController(SynchronizationContext sc) 
    { 
     _SynchronizationContext = sc; 
     ... 

それからあなたのオブジェクトにそれを取得する必要があります。イベントを発生させるために、あなたは、この操作を行います:

var evt = this.PropertyChanged; 
if (evt != null) sc.Send(
    new SendOrPostCallback(state => evt(this, ...EventArgs...)), 
    null); 
+0

私のEmployeeクラスのメソッド "_myPay_PropertyChanged()" this.PropertyChanged(これは新しいPropertyChangedEventArgs( "Salary"))で動作しました。同じ例外がスローされています。また、私はその呼び出しを呼び出す必要がありますか? – Stephen

+0

はい - 回答を更新しました。 –

+0

また、EmployeeControllerにイベントベースの非同期モデルを実装することもできます。この場合、EmployeeControllerがrigheスレッドで呼び出しを行うため、これを行う必要はありません。もう少し時間をかけて説明しますが、もっとうまく解決できます。 –

関連する問題