跨线程使用引用的对象
我正在做的是创建一个对象(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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
问题是 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.
即使当
InvokeRequired == true
时,您也会调用empList.Add(empl);
。尝试:您还需要在 UI 线程上引发
INotifyPropertyChanged
事件,但您没有 UI 控件来调用Invoke
。执行此操作的简单方法是存储对主表单的引用并将其设置为public static
:然后您可以使用
Form1.UI.InvokeRequired
和Form1。从应用程序中的任何位置进行 UI.Invoke
。我试图一次迈出一步,但如果您想要更正确的解决方案,您可以将 UI
SynchronizationContext
传递给控制器并使用其Post
或Send
方法:然后您必须将其发送到您的对象。要引发事件,您可以执行以下操作:
You are calling
empList.Add(empl);
even whenInvokeRequired == true
. Try:You also need to raise your
INotifyPropertyChanged
events on the UI thread, but you do not have a UI Control to callInvoke
on. The easy way to do this is to store a reference to your main form and make itpublic static
:You can then use
Form1.UI.InvokeRequired
andForm1.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 itsPost
orSend
methods:You then have to get it to your objects. To raise an event you would then do this: