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