Использовать ссылочный объект в потоках - PullRequest
5 голосов
/ 30 декабря 2011

То, что я делаю, - это создание объекта (A), который содержит ссылку на другой объект (B). Часть пользовательского интерфейса моего кода содержит эти объекты (A) в BindingList, который используется в качестве источника данных для представления сетки DevExpress. Контроллер отправляет вновь созданные объекты (A) в пользовательский интерфейс через событие. Контроллер также имеет поток, который обновляет ссылочный объект (B). Выданное исключение происходит из DevExpress GridView и читает «Обнаружена перекрестная операция. Чтобы подавить это исключение, установите DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true».

Теперь я не хочу подавлять это исключение, потому что код в конечном итоге окажется в критическом приложении.

Так как я могу обновить эталонный объект между потоками, не вызывая проблем? Вот код из моего тестового приложения. По сути, это будет то же самое в реальной программе.

UPDATE Ошибка в пользовательском интерфейсе была исправлена ​​ответом Николаса Батлера, но теперь исключение перешло в класс 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;
}

Класс сотрудника: (Исключение, выброшенное в этом классе)

    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 ]

2 голосов
/ 30 декабря 2011

Проблема в том, что EmployeeController.onNewEmployee запускается не в потоке пользовательского интерфейса.Используйте асинхронный шаблон на основе событий, чтобы вызвать события в определенном (в данном случае, пользовательском интерфейсе) потоке: http://msdn.microsoft.com/en-us/library/hkasytyf.aspx.

В качестве альтернативы вы можете проверить IsInvokeRequired в каждом обработчике событий и, если это так, использовать .Invoke, чтобы вернуться напоток пользовательского интерфейса. Это более громоздко, но в вашем случае может быть проще / быстрее реализовать.

1 голос
/ 30 декабря 2011

Вы звоните empList.Add(empl);, даже когда InvokeRequired == true. Попробуйте:

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

Вам также необходимо поднять ваши события INotifyPropertyChanged в потоке пользовательского интерфейса, но у вас нет элемента управления пользовательского интерфейса для вызова Invoke. Самый простой способ сделать это - сохранить ссылку на вашу основную форму и сделать ее public static:

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

    public Form1()
    {
        UI = this;
    }
}

Затем вы можете использовать Form1.UI.InvokeRequired и Form1.UI.Invoke из любой точки вашего приложения.


Я пытался сделать один шаг за раз, но если вам нужно более правильное решение, вы можете передать 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 );
...