完成生产者 - 消费者后继续使用方法

时间:2014-11-05 14:29:27

标签: c# task

我在WPF中有一个生产者 - 消费者应用程序。点击一个按钮后。

private async void Start_Click(object sender, RoutedEventArgs e)
{
     try
     {
        // set up data
        var producer = Producer();
        var consumer = Consumer();
        await Task.WhenAll(producer, consumer);
        // need log the results in Summary method 
        Summary();
     }
}

摘要方法是无效的;我认为这是正确的。

private void Summary(){}
async Task Producer(){ await something }
async Task Consumer(){ await something }

编辑:

我的问题是在Summary()方法中我必须使用任务中的计算值,但Consumer任务是一个长时间运行的过程。程序快速运行Summary甚至没有获取更新的值。它使用初始值。

我的想法:

await Task.WhenAll(producer, consumer);
Summary(); 

EDIT2:11:08 AM 11/05/2014

private void Summary()
{
     myFail = 100 - mySuccess;
     _dataContext.MyFail = myFail; // update window upon property changed

 async Task Consumer()
 {
     try
     {
         Dictionary<string, string> dict = new Dictionary<string, string>();
         var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions
         {
                MaxDegreeOfParallelism = 5,
                CancellationToken = cToken
         };
         var c = new ActionBlock<T>(
          t=>
         {
              if (cToken.IsCancellationRequested)
                  return;
               dict = Do(t, cToken);
               if(dict["Success"] == "Success")
                   mySuccess++;

目前的问题是mySuccess始终是Summary方法中的初始值。

1 个答案:

答案 0 :(得分:0)

生产者和消费者完成后,您可以使用ContinueWith方法执行Summary

Task.WhenAll(producer, consumer)
    .ContinueWith(continuation => Summary());

编辑1

您似乎正在滥用或使用错误的生产者/消费者模式。

生产者应该生成值并将它们铲入通信管道的一端。在管道的另一端,消费者在可用时消耗这些值。换句话说,消费者等待让生产者产生一些价值并将价值放在管道中,并使价值到达管道的末端。

通常这涉及某种信令机制,其中生成者在创建值时发出信号(唤醒)消费者。

在您的情况下,您没有信号机制,我强烈怀疑您的生产者只生成一个值。如果是后一种情况,您只需从&#34;生产者&#34;。

返回一个值

但是,如果您的生产者正在创建多个值,则可以使用BlockingCollection<T>类将值从生产者发送到消费者。

Producer课程中,获取对管道的引用并将数据放入其中:

public class Producer
{
    private BlockingCollection<Data> _pipe;

    public void Start()
    {
        while(!done)
        {
            var value = ProduceValue();
            _pipe.Add(value);
        }
        // Signal the consumer that we're finished
        _pipe.CompleteAdding();
    }
}

Consumer类中等待值到达并处理每个值:

public class Consumer
{
    private BlockingCollection<Data> _pipe;

    public void Start()
    {
        foreach(var value in _pipe.GetConsumingEnumerable())
        {
            // GetConsumingEnumerable will block until a value arrives and 
            // will exit when producer calls CompleteAdding()
            Process(value);
        }
    }
}

完成上述操作后,您可以使用ContinueWith方法awaitWhenAll来运行Summary

编辑2

正如评论中所承诺的,我已经分析了您在MSDN Forum上发布的代码。代码中有几个问题。

首先,最简单的解决方法是,您不以线程安全的方式递增计数器。增量(value++)不是原子操作,因此在增加共享字段时应该小心。一个简单的方法是:

Interlocked.Increment(ref evenNumber);

现在,代码中存在实际问题:

  1. 正如我前面提到的,消费者不知道生产者何时完成了产生价值。因此,在生产者退出for块之后,它应该表示它已经完成。消费者等待生产者的结束信号;否则它将永远等待下一个值,但不会成为一个。

  2. 您正在将BufferBlock与开始执行的消费者代码相关联,但您并未等待消费者群体完成 - 您只需等待0.5秒钟退出使用者方法,让消费者块的工作线程徒劳地完成工作。

  3. 由于上述原因,您的Report方法在处理完成之前执行,在方法执行时输出evenNumber计数器的值,而不是在所有处理完成时

  4. 以下是带有一些注释的已编辑代码:

    class Program
    {
        public static BufferBlock<int> m_Queue = new BufferBlock<int>(new DataflowBlockOptions { BoundedCapacity = 1000 });
        private static int evenNumber;
    
        static void Main(string[] args)
        {
            var producer = Producer();
            var consumer = Consumer();
    
            Task.WhenAll(producer, consumer).Wait();
            Report();
        }
    
        static void Report()
        {
            Console.WriteLine("There are {0} even numbers", evenNumber);
            Console.Read();
        }
    
        static async Task Producer()
        {
            for (int i = 0; i < 500; i++)
            {
                // Send a value to the consumer and wait for the value to be processed
                await m_Queue.SendAsync(i);
            }
            // Signal the consumer that there will be no more values
            m_Queue.Complete();
        }
    
        static async Task Consumer()
        {
            var executionDataflowBlockOptions = new ExecutionDataflowBlockOptions
            {
                MaxDegreeOfParallelism = 4
            };
            var consumerBlock = new ActionBlock<int>(x =>
            {
                int j = DoWork(x);
                if (j % 2 == 0) 
                    // Increment the counter in a thread-safe way
                    Interlocked.Increment(ref evenNumber);
            }, executionDataflowBlockOptions);
    
            // Link the buffer to the consumer
            using (m_Queue.LinkTo(consumerBlock, new DataflowLinkOptions { PropagateCompletion = true }))
            {
                // Wait for the consumer to finish.
                // This method will exit after all the data from the buffer was processed.
                await consumerBlock.Completion;
            }
        }
    
        static int DoWork(int x)
        {
            Thread.Sleep(100);
            return x;
        }
    }