数据绑定和跨线程异常
Data Binding And Cross-Thread Exception
试图弄清楚这段代码中的缺陷:
场景 1:
此场景使用数据绑定并在 PriceSimulator
中的 NotifyPropertyChanged()
方法中导致众所周知的 跨线程异常 class.
场景二:
此方案通过订阅 PriceSimulator
的 PropertyChanged
事件解决了问题,消除了跨线程问题,但必须完全避免数据绑定。
假设场景 1 是预期的场景,并且假设不了解 PriceSimulator
的内部工作原理,只想绑定到 Price
属性,核心是什么问题在这里?
Form1.cs:
public partial class Form1 : Form
{
PriceSimulator simul;
Action labelAction;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
labelAction = new Action(SetLabel);
simul = new PriceSimulator(5, 1000);
//Scenario 1:
//Use data binding and get Cross-Thread exception
//label1.DataBindings.Add("Text", simul, "Price");
//Scenario 2:
//This works fine
//Subscribe to PropertyChanged event
simul.PropertyChanged += task_PropertyChanged;
simul.Start();
}
//Scenario 2:
void task_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (label1.InvokeRequired)
Invoke(labelAction);
else SetLabel();
}
private void SetLabel()
{
label1.Text = simul.Price.ToString("C2");
}
}
PriceSimulator.cs:
public class PriceSimulator : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int max, delay, priceValue;
private Timer timer;
public PriceSimulator(int max, int delay)
{
this.max = max;
this.delay = delay;
}
public void Start()
{
timer = new Timer(CallbackProc, null, delay, delay);
}
private void CallbackProc(object obj)
{
if (++Price >= max)
timer.Dispose();
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
try
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
timer.Dispose();
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
public int Price
{
get
{
return priceValue;
}
set
{
if (priceValue != value)
{
priceValue = value;
NotifyPropertyChanged();
}
}
}
}
您的 PriceSimulator 中必须有当前上下文 class:
private readonly SynchronizationContext _context = SynchronizationContext.Current;
现在您有了上下文,可以使用它来更新 UI:
_context.Post(delegate
{
if (++Price >= max)
timer.Dispose();
}, null);
试图弄清楚这段代码中的缺陷:
场景 1:
此场景使用数据绑定并在 PriceSimulator
中的 NotifyPropertyChanged()
方法中导致众所周知的 跨线程异常 class.
场景二:
此方案通过订阅 PriceSimulator
的 PropertyChanged
事件解决了问题,消除了跨线程问题,但必须完全避免数据绑定。
假设场景 1 是预期的场景,并且假设不了解 PriceSimulator
的内部工作原理,只想绑定到 Price
属性,核心是什么问题在这里?
Form1.cs:
public partial class Form1 : Form
{
PriceSimulator simul;
Action labelAction;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
labelAction = new Action(SetLabel);
simul = new PriceSimulator(5, 1000);
//Scenario 1:
//Use data binding and get Cross-Thread exception
//label1.DataBindings.Add("Text", simul, "Price");
//Scenario 2:
//This works fine
//Subscribe to PropertyChanged event
simul.PropertyChanged += task_PropertyChanged;
simul.Start();
}
//Scenario 2:
void task_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (label1.InvokeRequired)
Invoke(labelAction);
else SetLabel();
}
private void SetLabel()
{
label1.Text = simul.Price.ToString("C2");
}
}
PriceSimulator.cs:
public class PriceSimulator : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int max, delay, priceValue;
private Timer timer;
public PriceSimulator(int max, int delay)
{
this.max = max;
this.delay = delay;
}
public void Start()
{
timer = new Timer(CallbackProc, null, delay, delay);
}
private void CallbackProc(object obj)
{
if (++Price >= max)
timer.Dispose();
}
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
try
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
catch (Exception ex)
{
timer.Dispose();
System.Windows.Forms.MessageBox.Show(ex.Message);
}
}
public int Price
{
get
{
return priceValue;
}
set
{
if (priceValue != value)
{
priceValue = value;
NotifyPropertyChanged();
}
}
}
}
您的 PriceSimulator 中必须有当前上下文 class:
private readonly SynchronizationContext _context = SynchronizationContext.Current;
现在您有了上下文,可以使用它来更新 UI:
_context.Post(delegate
{
if (++Price >= max)
timer.Dispose();
}, null);