【问题标题】:Asynchronously add value to Bindinglist/Cross-threading & Locking issue为绑定列表/跨线程和锁定问题异步添加值
【发布时间】:2012-09-14 00:21:21
【问题描述】:

我有一个绑定到 datgridview 的 BindingList 数据。我用它来跟踪一些实时价格。各种线程每秒多次调用方法“update(Quote quote)”。如果 datagridview 不包含引用,则添加它。如果是,则更新报价的值。我不希望相同的引号出现在 BindingList (或 GUI 上)两次,所以我尝试锁定检查值是否在列表中的操作。它不起作用!我究竟做错了什么?我尝试了两种不同的锁定方式,并且锁定在 String 对象上,而不仅仅是一个对象。问题肯定出在BeginInvoke(new MethodInvoker(delegate() { activeQuotes.Insert(0, quote); })); 调用中(这可能需要一些时间),但如果我进行同步,“add”方法会引发“cross-threading”错误。 . .我可以做些什么来避免跨线程错误,但确保锁也可以工作??

public BindingList<Quote> activeQuotes = new BindingList<Quote>();
object lockObject = "lockObject";

dataGridViewActive.DataSource = activeQuotes;


public void update(Quote quote)
{
//lock (lockObject)
if(Monitor.TryEnter(lockObject))
{
try
   {
    if (!activeQuotes.Contains(quote))
    {
       try
       {
          activeQuotes.Add(quote);
          AddQuote(quote);
       }
       catch (Exception ex)
       {
          Console.WriteLine("Datagridview!!!!!!");
       }
     }
 else
 {
   int index = activeQuotes.IndexOf(quote);
   activeQuotes[index].Bid = quote.Bid;
   activeQuotes[index].Ask = quote.Ask;
   activeQuotes[index].Mid = quote.Mid;
   activeQuotes[index].Spread = quote.Spread;
   activeQuotes[index].Timestamp = quote.Timestamp;
}
finally
{
    Monitor.Exit(lockObject);
}
}

private void AddQuote(Quote quote)
{
     if (this.InvokeRequired)
     {
                BeginInvoke(new MethodInvoker(delegate() { activeQuotes.Insert(0, quote); }));
                BeginInvoke(new MethodInvoker(delegate() { dataGridViewActive.Refresh(); }));
                BeginInvoke(new MethodInvoker(delegate() { dataGridViewActive.AutoResizeColumns(DataGridViewAutoSizeColumnsMode.AllCells); }));
    }
    else
    {
                activeQuotes.Add(quote);
                dataGridViewActive.Refresh();
                dataGridViewActive.AutoResizeColumns (DataGridViewAutoSizeColumnsMode.AllCells);
     }
}

如果能提供任何帮助,我将不胜感激。

谢谢。

【问题讨论】:

    标签: c# asynchronous locking multithreading


    【解决方案1】:

    我之前写的这段代码就可以了

    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            BindingListInvoked<Name> names;
    
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_Load(object sender, EventArgs e)
            {
                names = new BindingListInvoked<Name>(dataGridView1);
    
                dataGridView1.DataSource = names;
    
                new Thread(() => names.Add(new Name() { FirstName = "Larry", LastName = "Lan" })).Start();
                new Thread(() => names.Add(new Name() { FirstName = "Jessie", LastName = "Feng" })).Start();
            }
        }
    
        public class BindingListInvoked<T> : BindingList<T>
        {
            public BindingListInvoked() { }
    
            private ISynchronizeInvoke _invoke;
            public BindingListInvoked(ISynchronizeInvoke invoke) { _invoke = invoke; }
            public BindingListInvoked(IList<T> items) { this.DataSource = items; }
            delegate void ListChangedDelegate(ListChangedEventArgs e);
    
            protected override void OnListChanged(ListChangedEventArgs e)
            {
    
                if ((_invoke != null) && (_invoke.InvokeRequired))
                {
                    IAsyncResult ar = _invoke.BeginInvoke(new ListChangedDelegate(base.OnListChanged), new object[] { e });
                }
                else
                {
                    base.OnListChanged(e);
                }
            }
            public IList<T> DataSource
            {
                get
                {
                    return this;
                }
                set
                {
                    if (value != null)
                    {
                        this.ClearItems();
                        RaiseListChangedEvents = false;
    
                        foreach (T item in value)
                        {
                            this.Add(item);
                        }
                        RaiseListChangedEvents = true;
                        OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
                    }
                }
            }
        }
    
        public class Name
        {
            public string FirstName { get; set; }
            public string LastName { get; set; }
        }
    }
    

    【讨论】:

    • 这个解决方案效果很好,但是需要注意的是,这个方法public BindingListInvoked(IList&lt;T&gt; items) { this.DataSource = items; }最终在构造函数中调用了一个虚拟方法,与DataSource关联的OnChange属性,这是不受欢迎的,更多信息参见@987654321 @
    【解决方案2】:

    我认为您应该将您的 BeginInvoke 更改为 Invoke。您需要在 UI 线程上获取它,而不是开始异步操作。否则,您的锁可能会在调用BeginInvoke 目标之前被释放,因为在调用BeginInvoke 时会立即返回控制。调用 Invoke 将阻塞该调用中的线程,直到 Invoke 目标完成,然后将控制权返回给您的线程,这将确保保持锁定。

    另外,您是否考虑过使用lock 块而不是Monitor 方法调用?它基本上是同一件事,但阻止你需要 try/finally。我没有看到您正在使用任何重试或从TryEnter 中受益,但也许代码示例没有证明这一点。

    【讨论】:

    • 抱歉,您的评论回复晚了。我认为BindingList 仍然是绑定到 Windows 窗体控件的正确方法。您可以尝试在 WPF 中使用ObservableCollection,但这仍然不是线程安全的。在 WPF 4.5 中,我相信他们正在实现自动 UI 线程调用集合更改。现在 .Net 中有线程安全的集合(ConcurrentQueue,ConcurrentStack),但在我看来,它们的功能似乎不如您的标准列表。
    • 使用BindingList 不需要实现INotifyPropertyChanged,但可以帮助自动通知UI 绑定更改。 BindingList 还允许您向DataGridView 添加排序。
    猜你喜欢
    • 1970-01-01
    • 2011-03-01
    • 2011-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-28
    • 2018-07-28
    • 1970-01-01
    相关资源
    最近更新 更多