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

C# 如何在TPL中实现与BlockingCollection类似的功能?,c#,task-parallel-library,C#,Task Parallel Library,我创建了一个包含BlockingCollection的简单类。它表示将按接收顺序执行的操作队列。我已经读了很多关于第三方物流的文章,似乎我应该用它来代替我目前正在使用的。一个原因是单元测试会更容易,编写的代码也会更少。我知道您可以使用Task.Factory.StartNew()之类的工具轻松地启动新任务,但不确定如何以与我当前拥有的类类似的方式使用它。我怎样才能用第三方物流完成同样的事情 根据要求,以下是我创建的类: public class MyService { /// <s

我创建了一个包含BlockingCollection的简单类。它表示将按接收顺序执行的操作队列。我已经读了很多关于第三方物流的文章,似乎我应该用它来代替我目前正在使用的。一个原因是单元测试会更容易,编写的代码也会更少。我知道您可以使用Task.Factory.StartNew()之类的工具轻松地启动新任务,但不确定如何以与我当前拥有的类类似的方式使用它。我怎样才能用第三方物流完成同样的事情

根据要求,以下是我创建的类:

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...
                }
            }
        });
    }
}
公共类MyService
{
///要在单独线程上使用的操作队列
私有BlockingCollection队列=新建BlockingCollection();
公共MyService()
{
StartService();
}
public void AddToQueue(MyObject newObject)
{
添加(newObject);
}
私有void StartService()
{
System.Threading.Tasks.Task.Factory.StartNew(()=>
{
while(true)
{
尝试
{
MyObject MyObject=queue.Take();//阻塞,直到收到新对象为止
//做工作。。。
}
捕获(例外e)
{
//日志。。。
}
}
});
}
}

阻止收藏
和异步收藏系列,用于简化生产者-消费者场景。(例如,一个作者和多个读者)

当然-您可以使用
任务来构建几乎相同的内容。运行将添加、删除、清理等的
。。将项目添加到非同步集合,如
列表
,但随后您必须自己管理所有多线程问题(存在很多问题)

例如:

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...
            }
        }
    }
}
公共类MyService
{
///要在单独线程上使用的操作队列
私有BlockingCollection队列=新建BlockingCollection();
private IEnumerable readers=Enumerable.Range(0,10)。选择((t)=>newtask(()=>this.StartService());
公共MyService()
{
StartService();
readers.aspallel().ForAll(t=>t.Start());
}
public void AddToQueue(MyObject newObject)
{
添加(newObject);
}
私有void StartService()
{
while(true)
{
尝试
{
MyObject MyObject=queue.Take();//阻塞,直到收到新对象为止
//做工作。。。
}
捕获(例外e)
{
//日志。。。
}
}
}
}

你看,在同一个集合中有多个“阅读器”。如果您自己做过
BlockingCollection
,您应该处理集合周围的所有
lock
s等等。

旧的方式,阻塞同步和基于任务的异步不能很好地混合

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();
    }
}
公共类MyService
{
///要由单独任务使用的操作队列
私有ConcurrentQueue=新ConcurrentQueue();
私有bool\u isRunning=false;
私人任务(consumingTask),;
公共MyService()
{
}
public void AddToQueue(MyObject newObject)
{
添加(newObject);
}
私有void StartService()
{
_isRunning=true;
Task.Run(异步()=>
{
当(\u正在运行)
{
肌体肌体;
while(_isRunning&&queue.TryDequeue(out myObject)
{
尝试
{
//做工作。。。
}
捕获(例外e)
{
//日志。。。
}
}
wait Task.Delay(100);//将此值调整为与您的用例相关的值
}
});
}
公共服务
{
_isRunning=false;
_consumingTask.Wait();
}
}
此实现从不阻塞,只在真正需要计算时占用线程。它还很容易将其他
任务
优雅地混合在一起


TLDR:如果你按照
任务的方式去做,那就一直去做。中间点并不是你想去的地方,你会得到所有的复杂性而没有任何好处。

小心
开始新的
,如果你没有正确使用它,就使用
任务.Run(
相反,除非您有特定的理由使用
StartNew
。此外,还可以在方向上给您一个大致的提示(我不知道这是否是您需要的)但是看看。你能举一个你想做的代码的例子吗?我只是添加了代码来让它更清楚。接受@ScottChamberlain的建议,看看TPL数据流。它允许你建立一个管道来推动你的数据通过,看起来它会解决你的问题。不太确定你在寻找什么,但不会是
ConcurrentQueue
做这个把戏吗?这是一个FIFO收集和线程安全…或者收集需要保持持久性吗?所以在这种情况下最好使用BlockingCollection?@Andrew Yes。这个答案给了你一个多消费者的例子,你的代码只有一个消费者。如果你只需要一个消费者,你的代码就可以了。也许可以如果您希望能够关闭服务,请使用cellation token。建议使用<