【问题标题】:Databinding to a BlockingCollection数据绑定到 BlockingCollection
【发布时间】:2014-04-07 14:38:35
【问题描述】:

我正在尝试实现以下要求(C# 4.0);

  • 一个提交上传请求的“生产者”(在 UI 线程上 - 由用户操作驱动)
  • ListView 控件,数据绑定到待处理上传请求的集合
  • 处理请求 (FIFO) 并在项目状态更新或请求已完成时保持 UI 最新的消费者。

到目前为止,我还没有弄清楚如何在不使用两个集合的情况下完成上述操作,如下所示;

public void AddUploadRequest(UploadRequest uploadRequest)
{
    this.UploadRequests.Add(uploadRequest);
    this.uploadRequestsBlocking.Add(uploadRequest);
}

...其中UploadRequestsObservableCollection<UploadRequest>uploadRequestsBlockingBlockingCollection<UploadRequest>

完整的代码可以在这里找到; http://pastebin.com/620EqaY5(忽略 Dispatcher 的总注入 - 目前只是原型代码)

我通知我的 UI 更新如下;

this.dispatcher.Invoke(() => this.UploadRequests.Remove(uploadRequest));

有没有更好的方法来实现这个功能?或者,更重要的是,这种方法有什么严重的缺陷吗?

我正在考虑的一个可能的扩展是拥有多个消费者,通过使用Parallel.ForEach 而不是GetConsumingPartitioner(基于this 示例)。目前的方法是否会使这不合适?它确实工作正常,但我不能 100% 确信我在此过程中没有在某个地方犯下一些主要的线程错误。

【问题讨论】:

  • 你使用的是 C# 5.0 吗?
  • 不...我正在使用 C#4.0 - 我已更新问题以反映这一点。

标签: wpf c#-4.0 task-parallel-library


【解决方案1】:

您不能只将数据绑定到 BlockingCollection 的主要(唯一)原因是它没有实现 INotifyCollectionChanged,因此当添加/删除请求时 UI 不会更新。也就是说,有BlockingCollection wrappers around on the net 实现了 INotifyCollectionChanged。或者,您可以改为为 BlockingCollection 公开一个 CollectionView,而不是从 ObservableCollection 调度删除,只需调度 CollectionView 的 Refresh。

    public ICollectionView UploadRequestsView {get;set;}

    public UploadRequester(Dispatcher dispatcher)
    {
        this.dispatcher = dispatcher;           
        this.uploadRequestsBlocking = new BlockingCollection<UploadRequest>();

        UploadRequestsView = CollectionViewSource.GetDefaultView(uploadRequestsBlocking);

        this.consumerTask = Task.Factory.StartNew(this.ConsumeUploadRequests);
    }

    public void AddUploadRequest(UploadRequest uploadRequest)
    {
        uploadRequestsBlocking.Add(uploadRequest);
        UploadRequestsView.Refresh()
    }

    private void ConsumeUploadRequests()
    {
        foreach (var uploadRequest in this.uploadRequestsBlocking.GetConsumingEnumerable())
        {
            uploadRequest.Status = "Uploading...";

            Thread.Sleep(2000);
            uploadRequest.Status = "Successfully uploaded";

            Thread.Sleep(500);
            dispatcher.Invoke(() => UploadRequestsView.Refresh());
        }
    }

【讨论】:

  • 我是否正确地说我需要在“AddUploadRequest”调用中调用 UploadRequestsView.Refresh()(因此在每次添加时都使用调度程序)?
  • 感谢 Andrew - 这种方法似乎很适合我的用例。
  • 我很高兴这有帮助 - 要记住的一件事是,如果您有大量项目和添加/删除快速发生,这可以比 Collection Change 通知更容易挂起 UI因为刷新了整个列表而不是单个项目。避免这种情况的一种方法是将刷新限制为仅在需要时每 x 毫秒发生一次。
【解决方案2】:

我会使用TPL,并且不需要注入Dispatcher,因为TaskScheduler.FromCurrentSynchronizationContext()会给我MainUI线程的当前Dispatcher

public class UploadRequestProcessor
{
        public ObservableCollection<UploadRequest> UploadRequests { get; private set; }
        private readonly BlockingCollection<UploadRequest> uploadRequestsBlocking;
        private Task consumerTask;

        public UploadRequester()
        {
                this.UploadRequests = new ObservableCollection<UploadRequest>();
                this.uploadRequestsBlocking = new BlockingCollection<UploadRequest>();
                this.consumerTask = Task.Factory.StartNew(this.ConsumeUploadRequests).ContinueWith(nextTask => RemoveUploadRequests(nextTask.Result as BlockingCollection<UploadRequest>), TaskScheduler.FromCurrentSynchronizationContext());
        }

        public void AddUploadRequest(UploadRequest uploadRequest)
        {
                this.UploadRequests.Add(uploadRequest);
                this.uploadRequestsBlocking.Add(uploadRequest);
        }

        private void ConsumeUploadRequests()
        {
            var requestsToRemove = new BlockingCollection<UploadRequest>();
                foreach (var uploadRequest in this.uploadRequestsBlocking.GetConsumingEnumerable())
                {
                        uploadRequest.Status = "Uploading...";

                        Thread.Sleep(2000);
                        uploadRequest.Status = "Successfully uploaded";

                        Thread.Sleep(500);
                        requestsToRemove.Add(uploadRequest);

                }
            return requestsToRemove;
        }

        private void RemoveUploadRequests(BlockingCollection<UploadRequest> requestsToRemove)
        {
            foreach(var request in requestsToRemove)
            {
                UploadRequests.Remove(request);
            }
        }
}

【讨论】:

  • 这里的问题是我从来没有真正将我的 BlockingCollection 标记为完成,因此永远不会返回“requestsToRemove”。不过感谢 SynchronizationContext 提示 - 将考虑合并它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-04-05
  • 2011-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-11-01
相关资源
最近更新 更多