跨线程使用引用的对象

发布于 2024-12-24 02:13:47 字数 5721 浏览 1 评论 0原文

我正在做的是创建一个对象(A),它保存对另一个对象(B)的引用。我的代码的 UI 部分将这些对象 (A) 保存在 BindingList 中,用作 DevExpress 网格视图的数据源。控制器通过事件将新创建的对象 (A) 发送到 UI。控制器还有一个更新引用对象(B)的线程。抛出的异常来自 DevExpress GridView,并显示“检测到跨线程操作。要抑制此异常,请设置 DevExpress.Data.CurrencyDataController.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;
}

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

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

我非常感谢任何帮助。

What I am doing is creating an object(A) that holds a reference to another object(B). The UI portion of my code holds those objects(A) in a BindingList that is used as the data source for a DevExpress grid view. The controller sends the newly created objects(A) to the UI via an event. The controller also has a thread that updates the referenced object(B). The exception thrown comes from the DevExpress GridView and reads "Cross thread operation detected. To suppress this exception, set DevExpress.Data.CurrencyDataController.DisableThreadingProblemsDetection = true".

Now I don't want to suppress this exception because the code will eventually end up in a critical application.

So how can I update a reference object across threads without causing problems? Here is the code from my Test application. It will be essentially the same in the actual program.

UPDATE
The error in the UI was fixed by the answer from Nicholas Butler but now the exception has moved into the Employee class. I have updated the code to reflect the changes.

Here is my code

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

Employee Class: (Exception thrown in this class)

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

Employee Interface:

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

Pay Class:

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

I greatly appreciate any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

旧城空念 2024-12-31 02:13:47

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

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

The problem is that EmployeeController.onNewEmployee is being fired on a non UI thread. Use the event based async pattern to raise the events on a specific (in this case the UI) thread: http://msdn.microsoft.com/en-us/library/hkasytyf.aspx.

Alternatively you can check IsInvokeRequired in each event handler and if so use .Invoke to get back onto the UI thread.. This is more cumbersome but may in your case be easier/quicker to implement.

断舍离 2024-12-31 02:13:47

即使当 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 );

You are calling empList.Add(empl); even when InvokeRequired == true. Try:

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

You also need to raise your INotifyPropertyChanged events on the UI thread, but you do not have a UI Control to call Invoke on. The easy way to do this is to store a reference to your main form and make it public static:

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

    public Form1()
    {
        UI = this;
    }
}

You can then use Form1.UI.InvokeRequired and Form1.UI.Invoke from anywhere in your app.


I was trying to take one step at a time, but if you want a more correct solution, you can pass the UI SynchronizationContext to the controller and use its Post or Send methods:

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

class EmployeeController
{
    private SynchronizationContext _SynchronizationContext = null;

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

You then have to get it to your objects. To raise an event you would then do this:

var evt = this.PropertyChanged;
if ( evt != null ) sc.Send(
    new SendOrPostCallback( state => evt( this, ...EventArgs... ) ),
    null );
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文