通过单独的任务更新BindingSource中的元素

Har*_*lse 2 c# multithreading bindingsource

我有一个班,比如说人,有一个Id和一个名字.该类正确实现了INotifyPropertyChanged

另外:有人要求上课人.

我真正的问题是一个更复杂的课程,我把它简化为一个相当简单的POCO,以确定它不是因为我的班级.

本来:

public class Person
{
    public int Id {get; set;}
    public string Name {get; set;}
}
Run Code Online (Sandbox Code Playgroud)

对于更新,它需要实现INofityChanged.完整的代码就在这个问题的最后

StackOverflow:如何正确实现INotifyPropertyChanged

  • 我有一个System.Windows.Forms.Form
  • 此表单有一个BindingSource.
  • 绑定源的DataSource属性设置为我的类Person
  • 我有一个绑定到BindingSource的DataGridView
  • 我已经为绑定源添加了几个Person实例
  • 添加的人员被正确显示.
  • 如果我以编程方式更改bindingsource中的Person,则会正确显示更改的值.

到现在为止还挺好.如果在单独的线程中更改Person,则会出现问题.

我经常收到带有消息的InvalidOperationException

BindingSource不能是自己的数据源.不要将DataSource和DataMember属性设置为引用BindingSource的值.

我想这与更新是在一个等待的异步任务中完成的事实有关.我知道在更新用户界面项之前,您应该检查InvokeRequired是否相应地采取行动.

private void OnGuiItemChanged()
{
    if (this.InvokeRequired)
    {
       this.Invoke(new MethodInvoker(() => { OnGuiItemChanged(); }));
    }
    else
    {
        ... // update Gui Item
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,使用绑定源时,更改将在bindingsource内处理.所以我无法检查InvokeRequired

那么如何更新也存储在非UI线程中的绑定源中的项目?

按要求:类Person的实现和我的表单的一些代码

class Person : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int id = 0;
    private string name = null;
    public int Id
    {
        get { return this.id; }
        set { this.SetField(ref this.id, value); }
    }

    public string Name
    {
        get { return this.name; }
        set { this.SetField(ref this.name, value); }
    }

    protected void SetField<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        if (!EqualityComparer<T>.Default.Equals(field, value))
        {
            field = value;
            RaiseEventPropertyChanged(propertyName);
        }
    }

    private void RaiseEventPropertyChanged(string propertyName)
    {
        var tmpEvent = this.PropertyChanged;
        if (tmpEvent != null)
        {
            tmpEvent(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

表格的一些代码:

private void Form1_Load(object sender, EventArgs e)
{
    for (int i = 0; i < 10; ++i)
    {
        var person = new Person()
        {
            Id = i,
            Name = "William " + i.ToString(),
        };
        this.bindingSource1.Add(person);
    }
}

private void buttonStart_Click(object sender, EventArgs e)
{
    this.cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
    Task.Run(() => ChangePersonsAsync(this.cancellationTokenSource.Token));
}

private async Task ChangePersonsAsync(CancellationToken token)
{
    try
    {
        while (!token.IsCancellationRequested)
        {
            foreach (var p in this.bindingSource1)
            {
                Person person = (Person)p;
                person.Id = -person.Id;
            }
            await Task.Delay(TimeSpan.FromSeconds(0.01), token);
        }
    }
    catch (TaskCanceledException)
    {
    }
}
Run Code Online (Sandbox Code Playgroud)

Iva*_*oev 14

正如您所提到的,更改是在BindingSource类中处理的,因此我看到的最简单的方法是将其替换为以下内容

public class SyncBindingSource : BindingSource
{
    private SynchronizationContext syncContext;
    public SyncBindingSource()
    {
        syncContext = SynchronizationContext.Current;
    }
    protected override void OnListChanged(ListChangedEventArgs e)
    {
        if (syncContext != null)
            syncContext.Send(_ => base.OnListChanged(e), null);
        else
            base.OnListChanged(e);
    }
}
Run Code Online (Sandbox Code Playgroud)

只要确保它是在UI线程上创建的.