Исключения BindingSource и Cross-Thread

Чтобы объяснить эту проблему, я поместил все необходимое в небольшой пример приложения, которое, надеюсь, объясняет проблему. Я действительно старался втиснуть все в как можно меньше строк, но в моем реальном приложении эти разные актеры не знают друг друга и не должны. Таким образом, простой ответ вроде «возьмите переменную несколькими строками выше и вызовите для нее 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 () чтобы получить значение их. К сожалению, это не встроено в него (или я ошибаюсь?), Поэтому мой вопрос:

Как можно разрешить это исключение между потоками, если объект данных или рабочий поток ничего не знают об источнике привязки, который прослушивает свои события, чтобы поместить данные в графический интерфейс.

7
задан Oliver 16 August 2011 в 14:08
поделиться

1 ответ

Я столкнулся с аналогичной ситуацией, где я пытался удалить запись из обязательного источника, обязательный источник связывается с управлением 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 ()

0
ответ дан 7 December 2019 в 02:53
поделиться
Другие вопросы по тегам:

Похожие вопросы: