BlockingCollection + UI线程

时间:2010-10-19 20:06:36

标签: c# wpf concurrency

我跟着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个消费者并使它们并行运行。

这应该怎么做?

2 个答案:

答案 0 :(得分:2)

使用调度程序包装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;
}

只需从ConcurrentPriorityQueue派生DispatcherObject。我认为这是应该如何完成的。


更简单,只需编写NotifyCollectionChanged方法,如下所示:

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

然后您不必使用BeginInvoke将其他方法丢弃。

答案 1 :(得分:1)

[评论问题后,然后]

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