【问题标题】:BlockingCollection + UI ThreadBlockingCollection + UI 线程
【发布时间】:2010-10-19 20:06:36
【问题描述】:

我已经关注this tutorial,创建了一个优先队列并用阻塞集合包装了它。我有一个 DataGrid,我已将其连接到发出更改事件的底层优先级队列。我可以从 UI 线程将项目添加到集合中,而不会出现故障,并且当缓冲区已满时它会阻塞。

现在我该如何消费这些物品?这是我得到的:

public DownloadViewModel()
{
    Queue = new ConcurrentPriorityQueue<DownloadItem>(10);
    Buffer = new BlockingCollection<KeyValuePair<int, DownloadItem>>(Queue, 10000);

    Task.Factory.StartNew(() =>
    {
        KeyValuePair<int, DownloadItem> item;
        while(!Buffer.IsCompleted)
        {
            if(Buffer.TryTake(out item))
            {
                // do something with the item
            }

            Thread.SpinWait(100000);
        }
    });
}

但是,一旦我添加了 Task.Factory.StartNew 位,我的应用程序突然需要 30 秒才能出现窗口(在它是即时之前),当我添加一个项目时,我得到了异常

这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。

我理解,但真的有必要使用 UI 线程获取项目吗?这不是破坏了使用这个 BlockingCollection 的全部目的吗?我想创建 4 或 8 个消费者并让它们并行运行。

这应该怎么做?

【问题讨论】:

  • 真的只需要从 UI 线程更新 UI 控件。这就是异常告诉你的。是的,这很快就破坏了精心设计的线程方案的用处。
  • @Hans:当然,但是当阻塞队列尝试获取一个项目时,底层队列会发出一个更改通知,.. 为什么不能自动委托回 UI 线程?为什么要阻止消费者?无论如何..我现在愿意接受任何解决方案,即使它的效率比我认为的要低一些。我想这是一个非常普遍的范例。生产者-消费者并不是什么新鲜事,向用户显示剩余的项目应该不难吗?
  • 在线程方面没有什么是自动或简单的。在列表中收集结果并使用 Dispatcher.BeginInvoke 更新 UI。
  • 消费者不应该被阻止......消费者应该消费这些物品,并且做他们所做的任何事情(下载一个项目?)并且这些项目的消费对UI造成的任何变化都应该从消费者发送到 UI。在 UI 线程上生产和消费会有点傻——这肯定会破坏多线程的整个目的吗? (没有时间关注这篇文章或制作一个工作示例,所以恐怕不是一个完整的答案)
  • @Giraffe:是的,这正是我想说的。消费者不应该因为等待更新 UI 而成为瓶颈。

标签: c# wpf concurrency


【解决方案1】:

使用调度程序包装 CollectionChanged 事件似乎工作得很好......

public bool TryAdd(KeyValuePair<int, T> item)
{
    int pos = _queues.Take(item.Key + 1).Sum(q => q.Count);
    _queues[item.Key].Enqueue(item.Value);
    Interlocked.Increment(ref _count);
    Dispatcher.BeginInvoke(
        new Action(
            () =>
            NotifyCollectionChanged(
                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, pos))
        ));
    return true;
}

只需从DispatcherObject 派生我的ConcurrentPriorityQueue。我认为应该这样做。


更简单,只需像这样编写NotifyCollectionChanged 方法:

private void NotifyCollectionChanged(NotifyCollectionChangedEventArgs e)
{
    lock (CollectionChanged)
    {
        if (CollectionChanged != null)
            Dispatcher.BeginInvoke(new Action(() => CollectionChanged(this, e)));
    }
}

然后您就不必在其他方法中乱扔BeginInvoke

【讨论】:

  • 你能发布一个代码示例(MainWindow.xaml.cs)吗?
  • @Fulproof:找不到使用此类的示例,但这是完整的类:goo.gl/9BWb7
  • 谢谢,但是您提供的类代码给出了编译错误“非静态字段、方法或属性需要对象引用 'System.Windows.Threading.Dispatcher.Invoke(System.Delegate , 参数对象[])'"
  • @Fulproof:奇怪...我想知道自从我写了之后线程 API 是否发生了一些变化?现在已经超过 2 岁了 ;) 或者也许它普通的旧不起作用,我不知道。很久没碰那个项目了。
【解决方案2】:

[对问题发表评论后,然后]

您不需要“使用 UI 线程获取项目”。但是,由于处理消费任务中的项目而对 UI 的任何更新都需要分派到 UI 线程。分开你的顾虑!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多