2011-12-30 16 views
5

Quello che sto facendo è creare un oggetto (A) che contiene un riferimento a un altro oggetto (B). La parte dell'interfaccia utente del mio codice contiene quegli oggetti (A) in un BindingList che viene utilizzato come origine dati per una vista griglia DevExpress. Il controller invia gli oggetti appena creati (A) all'interfaccia utente tramite un evento. Il controller ha anche una discussione che aggiorna l'oggetto di riferimento (B). L'eccezione generata da DevExpress GridView e legge "Operazione cross thread rilevata. Per sopprimere questa eccezione, impostare DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true".Utilizzare un oggetto di riferimento attraverso i thread

Ora non voglio sopprimere questa eccezione perché il codice finirà per finire in un'applicazione critica.

Quindi, come posso aggiornare un oggetto di riferimento attraverso i thread senza causare problemi? Ecco il codice dalla mia applicazione di test. Sarà essenzialmente lo stesso nel programma attuale.

UPDATE L'errore nell'interfaccia utente è stato fissato dalla risposta da Nicholas Butler, ma ora l'eccezione si è spostato nella classe Employee. Ho aggiornato il codice per riflettere le modifiche.

Ecco il mio codice

* 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(); 
    } 
} 

Controller:

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

classe Employee: (Eccezione generata in questa classe)

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

interfaccia dei dipendenti:

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

Pay Classe:

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

apprezzo molto di aiuto.

risposta

2

Il problema è che si sta EmployeeController.onNewEmployee sparato su un thread non UI. Utilizzare il modello asincrono basato su eventi per aumentare gli eventi su un thread specifico (in questo caso l'interfaccia utente): http://msdn.microsoft.com/en-us/library/hkasytyf.aspx.

In alternativa è possibile controllare IsInvokeRequired in ogni gestore di eventi e, in tal caso, utilizzare .Invoke per tornare al thread dell'interfaccia utente. Ciò è più ingombrante ma potrebbe essere nel caso più facile/più veloce da implementare.

1

Si chiama empList.Add(empl); anche se InvokeRequired == true. Prova:

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

È inoltre necessario alzare le INotifyPropertyChanged eventi sul thread dell'interfaccia utente, ma non si dispone di un controllo dell'interfaccia utente per chiamare Invoke su. Il modo più semplice per farlo è quello di memorizzare un riferimento al form principale e renderlo public static:

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

    public Form1() 
    { 
     UI = this; 
    } 
} 

È quindi possibile utilizzare Form1.UI.InvokeRequired e Form1.UI.Invoke da qualsiasi punto della app.


Stavo cercando di fare un passo alla volta, ma se si vuole una soluzione più corretta, è possibile passare l'interfaccia utente SynchronizationContext al controller e utilizzare i suoi Post o Send metodi:

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

class EmployeeController 
{ 
    private SynchronizationContext _SynchronizationContext = null; 

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

Devi quindi portarlo ai tuoi oggetti. Per generare un evento si sarebbe poi esegue questa operazione:

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

Ah che ha funzionato ma ora nella mia classe Employee con il metodo "_myPay_PropertyChanged()" this.PropertyChanged (this, new PropertyChangedEventArgs ("Salary")); sta lanciando la stessa eccezione. Devo anche chiamare un invoke su questo? – Stephen

+0

Sì, ho aggiornato la mia risposta. –

+0

Oppure potresti implementare il modello asincrono basato su evento nel tuo EmployeeController e non dovresti fare nulla di tutto ciò in quanto EmployeeController sarebbe responsabile di effettuare le chiamate sul thread delle righe. Prende un po 'più tempo per capire, ma una soluzione molto più ordinata. –

Problemi correlati