跨线程使用引用的对象

时间:2011-12-30 18:39:37

标签: c# multithreading devexpress pass-by-reference

我正在做的是创建一个对象(A),它包含对另一个对象(B)的引用。我的代码的UI部分将这些对象(A)保存在BindingList中,该BindingList用作DevExpress网格视图的数据源。控制器通过事件将新创建的对象(A)发送到UI。控制器还有一个更新引用对象(B)的线程。抛出的异常来自DevExpress GridView并读取“检测到跨线程操作。要抑制此异常,请设置DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true”。

现在我不想压制此异常,因为代码最终会在关键应用程序中结束。

那么如何在不引起问题的情况下跨线程更新引用对象?这是我的Test应用程序的代码。它在实际程序中基本相同。

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

员工类:(此类中抛出异常)

    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 个答案:

答案 0 :(得分:2)

问题是在非UI线程上触发了EmployeeController.onNewEmployee。使用基于事件的异步模式来引发特定(在本例中为UI)线程上的事件:http://msdn.microsoft.com/en-us/library/hkasytyf.aspx

或者,您可以在每个事件处理程序中检查IsInvokeRequired,如果是这样,请使用.Invoke返回到UI线程..这样更麻烦,但在您的情况下可能更容易/更快地实现。

答案 1 :(得分:1)

即使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
    }
}

您还需要在UI线程上引发INotifyPropertyChanged事件,但您没有用于调用Invoke的UI控件。这样做的简单方法是存储对主表单的引用并将其设为public static

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

    public Form1()
    {
        UI = this;
    }
}

然后,您可以在应用中的任何位置使用Form1.UI.InvokeRequiredForm1.UI.Invoke


我尝试一步一步,但如果您想要更正确的解决方案,可以将用户界面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 );