Чтобы объяснить эту проблему, я поместил все необходимое в небольшой пример приложения, которое, надеюсь, объясняет проблему. Я действительно старался втиснуть все в как можно меньше строк, но в моем реальном приложении эти разные актеры не знают друг друга и не должны. Таким образом, простой ответ вроде «возьмите переменную несколькими строками выше и вызовите для нее Invoke» не сработает.
Итак, давайте начнем с кода, а затем еще немного объяснений. Сначала есть простой класс, реализующий INotifyPropertyChanged:
public class MyData : INotifyPropertyChanged
{
private string _MyText;
public MyData()
{
_MyText = "Initial";
}
public string MyText
{
get { return _MyText; }
set
{
_MyText = value;
PropertyChanged(this, new PropertyChangedEventArgs("MyText"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
Так что ничего особенного. А вот пример кода, который можно просто поместить в любой пустой проект консольного приложения:
static void Main(string[] args)
{
// Initialize the data and bindingSource
var myData = new MyData();
var bindingSource = new BindingSource();
bindingSource.DataSource = myData;
// Initialize the form and the controls of it ...
var form = new Form();
// ... the TextBox including data bind to it
var textBox = new TextBox();
textBox.DataBindings.Add("Text", bindingSource, "MyText");
textBox.DataBindings.DefaultDataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged;
textBox.Dock = DockStyle.Top;
form.Controls.Add(textBox);
// ... the button and what happens on a click
var button = new Button();
button.Text = "Click me";
button.Dock = DockStyle.Top;
form.Controls.Add(button);
button.Click += (_, __) =>
{
// Create another thread that does something with the data object
var worker = new BackgroundWorker();
worker.RunWorkerCompleted += (___, ____) => button.Enabled = true;
worker.DoWork += (___, _____) =>
{
for (int i = 0; i < 10; i++)
{
// This leads to a cross-thread exception
// but all i'm doing is simply act on a property in
// my data and i can't see here that any gui is involved.
myData.MyText = "Try " + i;
}
};
button.Enabled = false;
worker.RunWorkerAsync();
};
form.ShowDialog();
}
Если вы запустите этот код, вы получите исключение между потоками, пытаясь изменить свойство MyText
. Это происходит, потому что объект MyData
вызывает PropertyChanged
, который будет перехвачен BindindSource
. Затем, в соответствии с привязкой
, он попытается обновить свойство Text
текстового поля TextBox
. Что явно приводит к исключению.
Моя самая большая проблема здесь связана с тем, что объект MyData
не должен ничего знать о графическом интерфейсе (потому что это простой объект данных ). Также рабочий поток ничего не знает о графическом интерфейсе. Он просто воздействует на кучу объектов данных и манипулирует ими.
ИМХО, я думаю, что BindingSource
должен проверить, в каком потоке находится объект-получатель, и выполнить соответствующий Invoke ()
чтобы получить значение их. К сожалению, это не встроено в него (или я ошибаюсь?), Поэтому мой вопрос:
Как можно разрешить это исключение между потоками, если объект данных или рабочий поток ничего не знают об источнике привязки, который прослушивает свои события, чтобы поместить данные в графический интерфейс.
Я столкнулся с аналогичной ситуацией, где я пытался удалить запись из обязательного источника, обязательный источник связывается с управлением UI, которое отвечает на изменения, внесенные на обязательном источнике.
я использовал решение от Muhammad в дополнительном методе.
/// <summary>
/// Executes on the UI thread, but calling thread waits for completion before continuing.
/// </summary>
public static void InvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
if (c.InvokeRequired)
c.Invoke(new Action(() => action(c)));
else
action(c);
}
Затем это может использоваться как это.
this.InvokeIfRequired(frm => frm.defaultBindingSource.Remove(rec));
Этот способ, которым требуется только одна строка кода везде, где управление или форма должны быть вызваны.
я также добавил расширение BeginInvokeIfRequired
/// <summary>
/// Executes asynchronously, on a thread pool thread.
/// </summary>
public static void BeginInvokeIfRequired<T>(this T c, Action<T> action) where T : Control
{
if (c.InvokeRequired)
c.BeginInvoke(new Action(() => { action(c); }));
else
action(c);
}
, как обсуждено здесь: различие между Вызывают () и BeginInvoke ()