BindingSource和跨线程异常

Oli*_*ver 7 .net c# multithreading winforms

为了解释这个问题,我把所需的一切都放在一个小样本应用程序中,希望能够解释这个问题 我真的试图尽可能减少所有内容,但在我的实际应用中,这些不同的演员彼此不认识,也不应该.因此,简单的回答,如"将变量放在上面的几行并调用它上面的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;
}
Run Code Online (Sandbox Code Playgroud)

所以没什么特别的.这里的示例代码可以简单地放入任何空的控制台应用程序项目中:

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();
}
Run Code Online (Sandbox Code Playgroud)

如果您要运行此代码,则会尝试更改MyText属性,从而获得跨线程异常.这会导致MyData对象调用PropertyChanged将被捕获BindindSource.然后,根据Binding,尝试更新的Text属性TextBox.这显然导致例外.

我最大的问题来自于这个MyData对象不应该知道关于gui的任何事情(因为它是一个简单的数据对象).工作者线程也不知道关于gui的任何信息.它只是作用于一堆数据对象并操纵它们.

恕我直言,我认为BindingSource应该检查接收对象生活在哪个线程,并做一个合适Invoke()的取得他们的价值.不幸的是,这不是内置的(或者我错了?),所以我的问题是:

如果数据对象和工作线程知道有关正在侦听其事件以将数据推送到gui的绑定源的任何信息,那么如何解决此跨线程异常.

Oli*_*ver 5

以上是解决此问题的上述示例的一部分:

button.Click += (_, __) =>
{
    // Create another thread that does something with the data object
    var worker = new BackgroundWorker();

    worker.DoWork += (___, _____) =>
    {
        for (int i = 0; i < 10; i++)
        {
            // This doesn't lead to any cross-thread exception
            // anymore, cause the binding source was told to
            // be quiet. When we're finished and back in the
            // gui thread tell her to fire again its events.
            myData.MyText = "Try " + i;
        }
    };

    worker.RunWorkerCompleted += (___, ____) =>
    {
        // Back in gui thread let the binding source
        // update the gui elements.
        bindingSource.ResumeBinding();
        button.Enabled = true;
    };

    // Stop the binding source from propagating
    // any events to the gui thread.
    bindingSource.SuspendBinding();
    button.Enabled = false;
    worker.RunWorkerAsync();
};
Run Code Online (Sandbox Code Playgroud)

所以这不会导致任何跨线程异常.这个解决方案的缺点是你不会在文本框中显示任何中间结果,但它总比没有好.