BindingList<T> привязан к источнику данных сетки через контроллер, затем обновлен из другого потока, исключение между потоками
Работая над JavaScript и Angular в течение последнего года, я сейчас работаю над проектом в Winforms. Я понимаю проблему перекрестных потоков с элементами управления пользовательского интерфейса (InvokeRequired), но раньше я не работал с BindingList, поэтому для эксперимента у меня есть базовое приложение с Form, Controller и POCO, которое реализует INotifyPropertyChanged. Форма вводится с контроллером, который имеет BindingList. Для имитации внешнего потока, манипулирующего данными, у меня есть метод Start внутри контроллера, который создает поток, запускает цикл и обновляет список акций. Очевидно, что это вызовет событие свойств измененного, которое уведомляет Grid и его источник данных, однако я получаю исключение между потоками. Что я хочу знать, что является обычной практикой для этого? Мой прошлый опыт был связан с потоками из GUI (фоновый рабочий), поэтому код обычно уже находится в форме и имеет доступ к "this" для вызова InvokeRequired. Однако при использовании BindingList из контроллера у меня этого нет.
Form1Controller
public class Form1Controller
{
public bool IsRunning { get; private set; }
private readonly Random _Random = new Random();
public Form1Controller()
{
Stocks = new BindingList<Stock>();
Stocks.Add(new Stock()
{
Name = "Microsoft",
Ticker = "MSFT",
Price = 25,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Google",
Ticker = "GOOG",
Price = 450,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Apple",
Ticker = "AAPL",
Price = 750,
Volume = 100000,
LastTraded = DateTime.Now
});
Stocks.Add(new Stock()
{
Name = "Oracle",
Ticker = "ORCL",
Price = 80,
Volume = 100000,
LastTraded = DateTime.Now
});
}
public BindingList<Stock> Stocks { get; set; }
public void Start()
{
IsRunning = true;
new Thread(() =>
{
while (IsRunning)
{
var index = _Random.Next(0, Stocks.Count);
Debug.WriteLine(index);
var stock = Stocks[index];
stock.Price += 1;
stock.Volume += 500;
stock.LastTraded = DateTime.Now;
Thread.Sleep(100);
}
}).Start();
}
public void Stop()
{
IsRunning = false;
}
}
Склад
public class Stock : DependencyObject
{
private string name;
public string Name
{
get { return name; }
set { SetField(ref name, value, () => Name); }
}
private string ticker;
public string Ticker
{
get { return ticker; }
set { SetField(ref ticker, value, () => Ticker); }
}
private int price;
public int Price
{
get { return price; }
set { SetField(ref price, value, () => Price); }
}
private long volume;
public long Volume
{
get { return volume; }
set { SetField(ref volume, value, () => Volume); }
}
private DateTime lastTraded;
public DateTime LastTraded
{
get { return lastTraded; }
set { SetField(ref lastTraded, value, () => LastTraded); }
}
}
DependencyObject
public class DependencyObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
protected bool SetField<T>(ref T field, T value, string propertyName)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
protected virtual void OnPropertyChanged<T>(Expression<Func<T>> selectorExpression)
{
if (selectorExpression == null)
throw new ArgumentNullException("selectorExpression");
MemberExpression body = selectorExpression.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
OnPropertyChanged(body.Member.Name);
}
protected bool SetField<T>(ref T field, T value, Expression<Func<T>> selectorExpression)
{
if (EqualityComparer<T>.Default.Equals(field, value)) return false;
field = value;
OnPropertyChanged(selectorExpression);
return true;
}
}
Form1
public partial class Form1 : Form
{
private readonly Form1Controller _Controller = new Form1Controller();
public Form1(Form1Controller controller)
{
InitializeComponent();
_Controller = controller;
_MainGrid.DataSource = _Controller.Stocks;
}
private void _StartButton_Click(object sender, EventArgs e)
{
_Controller.Start();
}
private void _StopButton_Click(object sender, EventArgs e)
{
_Controller.Stop();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
_Controller.Stop();
}
}
Как перенести измененное свойство обратно в поток пользовательского интерфейса из BindingList (при условии, что это то, что я должен просить)?