消费者/生产者对消费物品的订单和约束

时间:2012-12-11 11:32:35

标签: c#-4.0 concurrency task-parallel-library concurrent-programming

我有以下情况

  • 我正在编写一个处理文件(作业)的服务器
    • 文件有“前缀”和时间
    • 应根据时间(旧文件优先)处理文件,但也要考虑前缀(不能同时处理具有相同前缀的文件)
  • 我有一个线程(Task with Timer)监视目录并将文件添加到“队列”(生产者)
  • 我有几个消费者从“队列”(消费者)获取文件 - 他们应该符合上述规则。
    • 每个任务的工作都保存在某个列表中(这表示约束条件)
  • 有几个消费者,消费者的数量是在启动时确定的。

其中一个要求是能够优雅地阻止消费者(立即或让正在进行的流程完成)。

我沿着这条路走了一段路:

while (processing)
{
    //limits number of concurrent tasks
    _processingSemaphore.Wait(queueCancellationToken);  
    //Take next job when available or wait for cancel signal
    currentwork = workQueue.Take(taskCancellationToken);

    //check that it can actually process this work
    if (CanProcess(currnetWork)
    { 
        var task = CreateTask(currentwork)
        task.ContinueWith((t) => { //release processing slot });
    }
    else
       //release slot, return job? something else?
 }

取消令牌来源是来电者代码,可以取消。有两个是为了能够在不取消正在运行的任务的情况下停止排队。

我厌倦了将“队列”实现为BlockingCollection包装“安全”的SortedSet。一般的想法工作(按时间排序),除了我需要找到一个匹配约束的新工作的情况。如果我将作业返回队列并尝试再次使用,我将获得相同的作业。

可以从队列中取出作业,直到找到合适的作业,然后返回“非法”作业,但这可能会导致其他消费者处理无序作业的问题

另一种选择是传递一个简单的集合和一种锁定它的方法,然后根据当前的约束锁定并进行简单的搜索。同样,这意味着编写可能不是线程安全的代码。

任何其他可以提供帮助的建议/指针/数据结构?

2 个答案:

答案 0 :(得分:0)

我会用TPL Dataflow实现您的要求。看看你可以用Producer-Consumer pattern实现它的方式。我相信这将满足您的所有要求(包括取消消费者)。

编辑(对于那些不喜欢阅读文档的人,但是谁... ...

以下是如何使用TPL Dataflow实现要求的示例。这种实现的优点在于,消费者不会绑定到单个线程,只在需要处理数据时才使用池线程。

    static void Main(string[] args)
    {
        BufferBlock<string> source = new BufferBlock<string>();
        var cancellation = new CancellationTokenSource();
        LinkConsumer(source, "A", cancellation.Token);
        LinkConsumer(source, "B", cancellation.Token);
        LinkConsumer(source, "C", cancellation.Token);

        // Link an action that will process source values that are not processed by other 
        source.LinkTo(new ActionBlock<string>((s) => Console.WriteLine("Default action")));

        while (cancellation.IsCancellationRequested == false)
        {
            ConsoleKey key = Console.ReadKey(true).Key;
            switch (key)
            {
                case ConsoleKey.Escape:
                    cancellation.Cancel();
                    break;
                default:
                    Console.WriteLine("Posted value {0} on thread {1}.", key, Thread.CurrentThread.ManagedThreadId);
                    source.Post(key.ToString());
                    break;
            }
        }

        source.Complete();
        Console.WriteLine("Done.");
        Console.ReadLine();
    }

    private static void LinkConsumer(ISourceBlock<string> source, string prefix, CancellationToken token)
    {
        // Link a consumer that will buffer and process all input of the specified prefix
        var consumer = new ActionBlock<string>(new Action<string>(Process), new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = 1, SingleProducerConstrained = true, CancellationToken = token, TaskScheduler = TaskScheduler.Default });
        var linkDisposable = source.LinkTo(consumer, (p) => p == prefix);

        // Dispose the link (remove the link) when cancellation is requested.
        token.Register(linkDisposable.Dispose);
    }

    private static void Process(string arg)
    {
        Console.WriteLine("Processed value {0} in thread {1}", arg, Thread.CurrentThread.ManagedThreadId);

        // Simulate work
        Thread.Sleep(500);
    }

答案 1 :(得分:0)

我认为Hans是对的:如果你已经有一个线程安全的SortedSet(实现IProducerConsumerCollection,那么它可以在BlockingCollection中使用),那么你所需要的只是放置那些文件现在可以处理到集合中。如果您完成了一个使另一个文件可供处理的文件,则此时将其他文件添加到集合中,而不是更早。