使用IEnumerable源进行分区

时间:2017-09-21 13:20:55

标签: c# async-await task-parallel-library producer-consumer

我有类型IProducerConsumerCollection的ConcurrentQueue,即

IProducerConsumerCollection<Job> _queue = new ConcurrentQueue<Job>();

和将方法添加到_queue的producer方法和从_queue处理Job的consumer方法。现在在使用者方法中,我喜欢同时处理作业。下面是带有生产者和消费者方法的样本类的代码:

public class TestQueue
{
    IProducerConsumerCollection<Job> _queue = new ConcurrentQueue<Job>();
    private static HttpClient _client = new HttpClient();

    public TestQueue()
    {
        WorkProducerThread();
        WorkConsumerThread();
    }

    public void WorkConsumerThread()
    {
        if (_queue.Count > 0)
        {
            //At this point, 4 partitions are created but all records are in 1st partition only; 2,3,4 partition are empty
            var partitioner = Partitioner.Create(_queue).GetPartitions(4);

            Task t = Task.WhenAll(
             from partition in partitioner
             select Task.Run(async () =>
             {
                 using (partition)
                 {
                     while (partition.MoveNext())
                         await CreateJobs(partition.Current);
                 }
             }));

            t.Wait();

            //At this point, queue count is still 20, how to remove item from _queue collection when processed?
        }
    }

    private async Task CreateJobs(Job job)
    {
        HttpContent bodyContent = null;
        await _client.PostAsync("job", bodyContent);
    }



    public void WorkProducerThread()
    {
        if (_queue.Count == 0)
        {
            try
            {
                for (int i = 0; i < 20; i++)
                {
                    Job job = new Job { Id = i, JobName = "j" + i.ToString(), JobCreated = DateTime.Now };
                    _queue.TryAdd(job);
                }
            }
            catch (Exception ex)
            {
                //_Log.Error("Exception while adding jobs to collection", ex);
            }
        }
    }

}

public class Job
{
    public int Id { get; set; }
    public string JobName { get; set; }
    public DateTime JobCreated { get; set; }
}

有两个问题,

  1. Partitioner.Create(_queue).GetPartitions(4); Partitioner.GetPartions创建4个分区但所有记录仅在第一个分区中; 2,3,4分区是空的。我找不到,为什么会这样?理想情况下,所有4个分区每个应该有5个记录(因为总共有20个记录在队列中)。我在MSDN上阅读了有关分区的this文章,但没有得到任何线索。我还检查了this文章中的分区示例。

  2. 此外,我想在使用consumer方法处理后从_queue中删除该项目,并且只有一种方法_queue.TryTake方法来删除项目。我不知道如何删除项目以及分区?

  3. 我可以考虑任何替代方法来实现相同的结果。

    提前致谢。

1 个答案:

答案 0 :(得分:1)

  

Partitioner.Create(_queue).GetPartitions(4); Partitioner.GetPartions   创建4个分区但所有记录仅在第一个分区中; 2,3,4   分区是空的。

这不正确,您的队列条目正在正确分区。要进行验证,请稍微更改处理逻辑以记录正在执行工作的分区:

Task t = Task.WhenAll(
    from partition in partitioner.Select((jobs, i) => new { jobs, i })
    select Task.Run(async () =>
    {
        using (partition.jobs)
        {
            while (partition.jobs.MoveNext())
            {
                Console.WriteLine(partition.i);
                await CreateJobs(partition.jobs.Current);
            }
        }
    }));

您会注意到Console.WriteLine会将值从0写入3 - 表明它们正在被正确分区。

  

另外,我想在处理之后从_queue中删除该项目   消费者方法和_queue.TryTake方法只有一种方法   除去项目。我不知道如何删除项目以及分区?

您可以通过轻微重写来实现这一目标。主要更改是切换为BlockingCollection并添加this NuGet package以访问GetConsumingPartitioner

尝试一下:

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace Test
{
    public class TestQueue
    {
        BlockingCollection<Job> _queue = new BlockingCollection<Job>();
        private static HttpClient _client = new HttpClient();

        public TestQueue()
        {
            WorkProducerThread();
            WorkConsumerThread();
        }

        public void WorkConsumerThread()
        {
            if (!_queue.IsCompleted)
            {
                //At this point, 4 partitions are created but all records are in 1st partition only; 2,3,4 partition are empty
                var partitioner = _queue.GetConsumingPartitioner().GetPartitions(4);

                Task t = Task.WhenAll(
                 from partition in partitioner
                 select Task.Run(async () =>
                 {
                     using (partition)
                     {
                         while (partition.MoveNext())
                             await CreateJobs(partition.Current);
                     }
                 }));


                t.Wait();

                Console.WriteLine(_queue.Count);
            }
        }

        private async Task CreateJobs(Job job)
        {
            //HttpContent bodyContent = null;
            //await _client.PostAsync("job", bodyContent);
            await Task.Delay(100);
        }



        public void WorkProducerThread()
        {
            if (_queue.Count == 0)
            {
                try
                {
                    for (int i = 0; i < 20; i++)
                    {
                        Job job = new Job { Id = i, JobName = "j" + i.ToString(), JobCreated = DateTime.Now };
                        _queue.TryAdd(job);
                    }

                    _queue.CompleteAdding();
                }
                catch (Exception ex)
                {
                    //_Log.Error("Exception while adding jobs to collection", ex);
                }
            }
        }

    }

    public class Job
    {
        public int Id { get; set; }
        public string JobName { get; set; }
        public DateTime JobCreated { get; set; }
    }
    class Program
    {
        static void Main(string[] args)
        {
            var g = new TestQueue();

            Console.ReadLine();
        }
    }
}