C# 如何从后台线程正确更新数据绑定 datagridview

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/455766/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 04:02:54  来源:igfitidea点击:

How do you correctly update a databound datagridview from a background thread

c#multithreadingdata-bindingdatagridviewinotifypropertychanged

提问by Jason Coyne

I have a custom object that implements INotifyPropertyChanged. I have a collection of these objects where the collection is based on BindingList I have created a binding source for the collection, and set the datasources of the bindingsource and datagridview.

我有一个实现 INotifyPropertyChanged 的​​自定义对象。我有这些对象的集合,其中集合基于 BindingList 我为集合创建了一个绑定源,并设置了 bindingsource 和 datagridview 的数据源。

Everything works great, except I need to update properties on the custom object from background threads. when I do so, I get the following error :

一切都很好,除了我需要从后台线程更新自定义对象的属性。当我这样做时,我收到以下错误:

BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refere back to BindingSource

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

I found the following post that seems to have my exact problem (and solution?) but I cannot quite figure it out.

我发现以下帖子似乎有我的确切问题(和解决方案?),但我无法弄清楚。

http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/3566f7c7-eb47-422e-ab09-9549a18da360/

http://social.msdn.microsoft.com/forums/en-US/winformsdatacontrols/thread/3566f7c7-eb47-422e-ab09-9549a18da360/

I created and initialized the oper variables per the post in my business object, and then I put the two event functions into my collection class. This compiled correctly, but hangs without exception when run.

我根据业务对象中的帖子创建并初始化了 oper 变量,然后将两个事件函数放入我的集合类中。这编译正确,但在运行时无一例外地挂起。

I have seen many posts saying to use Invoke/Begin Invoke, but I am not calling any functions on the UI, just updating business objects, so I am not sure where I would put the invoke calls.

我看到很多帖子说要使用 Invoke/Begin Invoke,但我没有在 UI 上调用任何函数,只是更新业务对象,所以我不确定将调用调用放在哪里。

One restriction : I want the business object to remain unaware of who is displaying it (as there are multiple consumers) so sending in GUI references into the business object so that I am later able to call invoke using those references is not an option.

一个限制:我希望业务对象不知道谁在显示它(因为有多个消费者)所以将 GUI 引用发送到业务对象中,以便我以后能够使用这些引用调用 invoke 不是一种选择。

采纳答案by Jason Coyne

I found this class in a forum that works. Just use this instead of BindingList

我在一个有效的论坛中找到了这门课。只需使用它而不是 BindingList

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ComponentModel;
using System.Threading;

namespace Utility
{
    public class ThreadedBindingList<T> : BindingList<T>
    {
        SynchronizationContext ctx = SynchronizationContext.Current;

        protected override void OnAddingNew(AddingNewEventArgs e)
        {

            if (ctx == null)
            {
                BaseAddingNew(e);
            }
            else
            {
                ctx.Send(delegate
                {
                    BaseAddingNew(e);
                }, null);
            }
        }
        void BaseAddingNew(AddingNewEventArgs e)
        {
            base.OnAddingNew(e);
        }
        protected override void OnListChanged(ListChangedEventArgs e)
        {
           // SynchronizationContext ctx = SynchronizationContext.Current;
            if (ctx == null)
            {
                BaseListChanged(e);
            }
            else
            {
                ctx.Send(delegate
                {
                    BaseListChanged(e);
                }, null);
            }
        }
        void BaseListChanged(ListChangedEventArgs e)
        {
            base.OnListChanged(e);
        }
    } 
}

回答by VVS

Since I took the time to format the sample for my needs I might as well post it here as a readable reference. Nothing changed except formatting.

由于我花时间根据我的需要格式化示例,因此我不妨将其发布在这里作为可读参考。除了格式没有改变。

using System.ComponentModel; 
using System.Threading;

namespace Utility 
{
  public class ThreadedBindingList : BindingList 
  {
    SynchronizationContext ctx = SynchronizationContext.Current;
    protected override void OnAddingNew(AddingNewEventArgs e)
    {
      if (ctx == null)
      {
        BaseAddingNew(e);
      }
      else
      {
        ctx.Send(delegate { BaseAddingNew(e); }, null);
      }
    }

    void BaseAddingNew(AddingNewEventArgs e)
    {
      base.OnAddingNew(e);
    }

    protected override void OnListChanged(ListChangedEventArgs e)
    {
      // SynchronizationContext ctx = SynchronizationContext.Current;
      if (ctx == null)
      {
        BaseListChanged(e);
      }
      else
      {
        ctx.Send(delegate { BaseListChanged(e); }, null);
      }
    }

    void BaseListChanged(ListChangedEventArgs e)
    {
      base.OnListChanged(e);
    }
  }
}

回答by Jeremy Lakeman

Not quite thread safe, but this small change to the above answers could have a big impact if your background thread is modifying object properties faster than they can be displayed;

不太线程安全,但是如果您的后台线程修改对象属性的速度比显示它们的速度快,那么对上述答案的这个小改动可能会产生很大的影响;

protected override void OnListChanged(ListChangedEventArgs e)
{
  // SynchronizationContext ctx = SynchronizationContext.Current;
  if (ctx == null)
  {
    BaseListChanged(e);
  }
  else if(e.ListChangedType == ListChangedType.ItemChanged)
  {
    ctx.Post(delegate { BaseListChanged(e); }, null);
  }
  else
  {
    ctx.Send(delegate { BaseListChanged(e); }, null);
  }
}

Welcome any suggestions for reducing the number of posted calls if the same object has been modified more than once, and making sure any later send call will block until all posted calls have been processed.

如果同一个对象被多次修改,欢迎任何减少发布调用数量的建议,并确保任何稍后的发送调用都将阻塞,直到所有发布的调用都被处理。