如何在TPL中实现与BlockingCollection类似的功能?

时间:2016-02-22 15:24:28

标签: c# task-parallel-library

我创建了一个包含BlockingCollection的简单类。它表示将按接收顺序执行的操作队列。我已经阅读了很多关于TPL的文章,似乎我应该使用它而不是我目前正在使用的那些。一个原因是单元测试会更容易,而且编写的代码也会更少。我知道您可以使用Task.Factory.StartNew()等方式轻松启动新任务,但不确定如何以与我目前的类相似的方式使用它。如何用TPL完成同样的事情?

根据要求,这是我创建的课程:

public class MyService
{
    /// <summary>Queue of actions to be consumed on a separate thread</summary>
    private BlockingCollection<MyObject> queue = new BlockingCollection<MyObject>();

    public MyService()
    {
        StartService();
    }

    public void AddToQueue(MyObject newObject)
    {
        queue.Add(newObject);
    }

    private void StartService()
    {
        System.Threading.Tasks.Task.Factory.StartNew(() =>
        {
            while (true)
            {
                try
                {
                    MyObject myObject = queue.Take(); // blocks until new object received

                    // Do work...
                }
                catch (Exception e)
                {
                    // Log...
                }
            }
        });
    }
}

2 个答案:

答案 0 :(得分:0)

BlockingCollection以及为简单的生产者 - 消费者方案创建的异步集合系列。 (例如一位作家和多位读者)

当然 - 您可以设法使用Task.Run构建几乎相同的内容,将List<T>添加,删除,清理等项目添加到非同步集合中,但是您必须管理所有多个自己解决问题(并且存在很多问题)。

例如:

public class MyService
{
    /// <summary>Queue of actions to be consumed on a separate thread</summary>
    private BlockingCollection<MyObject> queue = new BlockingCollection<MyObject>();
    private IEnumerable<Task> readers = Enumerable.Range(0, 10).Select((t) => new Task(() => this.StartService()));

public MyService()
{
    StartService();
    readers.AsParallel().ForAll(t => t.Start());
}

public void AddToQueue(MyObject newObject)
{
    queue.Add(newObject);
}

private void StartService()
{
        while (true)
        {
            try
            {
                MyObject myObject = queue.Take(); // blocks until new object received

                // Do work...
            }
            catch (Exception e)
            {
                // Log...
            }
        }
    }
}

你看 - 同一个系列上有多个'读者'。如果你自己完成了BlockingCollection,你应该处理所有lock周围的集合等。

答案 1 :(得分:0)

旧时尚,阻止同步和基于任务的异步不能很好地混合。

Task.Run(() =>
    {
        while (true)
        {
            // some thing that sometimes blocks
        }
    });

只是一种奇特的写作方式

new Thread(() =>
   {
      while (true)
      {
          // some thing that sometimes blocks
      }
});

这两个都将永远占据一个主线。第一个将使用线程池中的一个,这应该比特别创建的更好,但是因为它从未在以后发布,所以好处消失了。

如果你想使用任务和TPL,并从中获益,你应该尽可能地避免任何阻塞。例如,您可以使用ConcurrentQueue作为后备队列,并执行以下操作:

public class MyService
{
    /// <summary>Queue of actions to be consumed by separate task</summary>
    private ConcurrentQueue<MyObject> queue = new ConcurrentQueue<MyObject>();

    private bool _isRunning = false;
    private Task _consumingTask;

    public MyService()
    {
    }

    public void AddToQueue(MyObject newObject)
    {
        queue.Add(newObject);
    }

    private void StartService()
    {
        _isRunning = true;
        Task.Run( async () =>
        {
            while (_isRunning )
            {
                MyObject myObject;

                while(_isRunning && queue.TryDequeue(out myObject)
                {
                    try
                    {
                        // Do work...
                    }
                    catch (Exception e)
                    {
                        // Log...
                    }
                }
                await Task.Delay(100); // tune this value to one pertinent to your use case 
            }
        });
    }

    public void StopService()
    {
        _isRunning = false;
        _consumingTask.Wait();
    }
}

这种实现永远不会阻塞,只在真正需要计算时占用一个线程。

}也很容易将其他Task与它混合在一起。

TLDR:如果你走Task方式,那就一直走。中间点真的不是你想要的,你将获得所有的复杂性,没有任何优势。