2011-12-30 47 views
5

我在做的是創建一個對象(A),該對象持有對另一個對象(B)的引用。我的代碼的UI部分將這些對象(A)保存在用作DevExpress網格視圖的數據源的BindingList中。控制器通過事件將新創建的對象(A)發送到UI。控制器還有一個線程來更新引用的對象(B)。拋出的異常來自DevExpress GridView並讀取「檢測到交叉線程操作。要抑制此異常,請設置DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true」。跨線程使用引用的對象

現在我不想壓制這個異常,因爲代碼最終會在關鍵應用程序中結束。

那麼如何跨線程更新引用對象而不會造成問題呢?這是我的測試應用程序的代碼。它在實際的程序中基本相同。

UPDATE UI中的錯誤由Nicholas Butler的答案解決,但現在異常已移入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; 
} 

Employee類:(例外在這個類拋出)

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事件,但你沒有一個UI控件調用Invoke上。最簡單的方式做,這是存儲到你的主窗體的引用,並使其public static

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

    public Form1() 
    { 
     UI = this; 
    } 
} 

然後你可以在你的應用程序中使用Form1.UI.InvokeRequiredForm1.UI.Invoke從任何地方。


我試圖採取在一步一個腳印,但如果你想有一個更正確的解決方案,可以通過UI SynchronizationContext到控制器,並使用其PostSend方法:

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(this,new PropertyChangedEventArgs(「Salary」));拋出相同的異常。我是否也需要調用一個調用? – Stephen 2011-12-30 18:50:11

+0

是的 - 我已經更新了我的答案。 – 2011-12-30 19:10:50

+0

或者您可以在您的EmployeeController中實現基於事件的異步模型,並且您不必執行任何操作,因爲EmployeeController將負責在線程上進行調用。花多一點時間去理解,但是一個更好的解決方案。 – 2011-12-30 19:16:11