跨线程使用引用对象

本文关键字:引用 对象 线程 | 更新日期: 2023-09-27 18:19:10

我正在做的是创建一个对象(A),该对象(A)持有对另一个对象(B)的引用。我的代码的UI部分在BindingList中保存这些对象(A),该BindingList用作DevExpress网格视图的数据源。控制器通过事件将新创建的对象(A)发送给UI。控制器也有一个线程更新被引用的对象(B)。抛出的异常来自DevExpress GridView并读取"检测到交叉线程操作"。要抑制此异常,设置devexpress . data . currencydataconcontroller . disablethreadingproblemsdetection = true"。

现在我不想抑制这个异常,因为代码最终会在一个关键的应用程序中结束。

那么我如何跨线程更新引用对象而不引起问题呢?这是我的测试应用程序的代码。在实际的程序中是基本相同的。

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;
}
我非常感谢任何帮助。

跨线程使用引用对象

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

或者你可以在每个事件处理程序中检查isinvokerrequirequired,如果是的话,使用.Invoke返回UI线程。

您正在呼叫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事件,但是您没有一个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 );