数据绑定到BlockingCollection

时间:2014-04-07 14:38:36

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

我正在尝试实现以下要求(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%有信心我在这个过程中没有提供过一些主要的线程故障。

2 个答案:

答案 0 :(得分:2)

您只能将数据绑定到BlockingCollection的主要原因是它没有实现INotifyCollectionChanged,因此在添加/删除请求时UI不会更新。也就是说,BlockingCollection wrappers around on the net实现了INotifyCollectionChanged。或者您可以为BlockingCollection公开CollectionView,而不是从ObservableCollection调度remove,只需调度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());
        }
    }

答案 1 :(得分:1)

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

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);
            }
        }
}