我最近听过很多关于.NET 4.0中TPL的播客。他们中的大多数使用任务描述下载图像或进行计算等后台活动,以便工作不会干扰GUI线程。
我处理的大多数代码都有更多的多生产者/单一消费者风格,其中来自多个来源的工作项必须排队,然后按顺序处理。一个示例是日志记录,其中来自多个线程的日志行被顺序化为单个队列,以便最终写入文件或数据库。来自任何单一来源的所有记录必须保持有序,并且同一时刻的记录应在最终输出中彼此“接近”。
因此,多个线程或任务或任何调用队列的任何东西:
lock( _queue ) // or use a lock-free queue!
{
_queue.enqueue( some_work );
_queueSemaphore.Release();
}
专用工作线程处理队列:
while( _queueSemaphore.WaitOne() )
{
lock( _queue )
{
some_work = _queue.dequeue();
}
deal_with( some_work );
}
为这些任务的消费者方面提供工作线程似乎总是合理的。我应该使用TPL中的某些构造来编写未来的程序吗?哪一个?为什么呢?
答案 0 :(得分:13)
您可以使用长时间运行的任务来处理Wilka建议的BlockingCollection中的项目。这是一个非常符合您的应用程序要求的示例。你会看到这样的输出:
Log from task B
Log from task A
Log from task B1
Log from task D
Log from task C
不是A,B,C& A的输出D看起来是随机的,因为它们取决于线程的开始时间,但B总是出现在B1之前。
public class LogItem
{
public string Message { get; private set; }
public LogItem (string message)
{
Message = message;
}
}
public void Example()
{
BlockingCollection<LogItem> _queue = new BlockingCollection<LogItem>();
// Start queue listener...
CancellationTokenSource canceller = new CancellationTokenSource();
Task listener = Task.Factory.StartNew(() =>
{
while (!canceller.Token.IsCancellationRequested)
{
LogItem item;
if (_queue.TryTake(out item))
Console.WriteLine(item.Message);
}
},
canceller.Token,
TaskCreationOptions.LongRunning,
TaskScheduler.Default);
// Add some log messages in parallel...
Parallel.Invoke(
() => { _queue.Add(new LogItem("Log from task A")); },
() => {
_queue.Add(new LogItem("Log from task B"));
_queue.Add(new LogItem("Log from task B1"));
},
() => { _queue.Add(new LogItem("Log from task C")); },
() => { _queue.Add(new LogItem("Log from task D")); });
// Pretend to do other things...
Thread.Sleep(1000);
// Shut down the listener...
canceller.Cancel();
listener.Wait();
}
答案 1 :(得分:5)
我知道这个答案大约晚了一年,但请看一下MSDN。
显示了如何从TaskScheduler类创建LimitedConcurrencyLevelTaskScheduler。通过将并发性限制为单个任务,然后应该按顺序处理您的任务:
LimitedConcurrencyLevelTaskScheduler lcts = new LimitedConcurrencyLevelTaskScheduler(1);
TaskFactory factory = new TaskFactory(lcts);
factory.StartNew(()=>
{
// your code
});
答案 2 :(得分:3)
我不确定TPL在您的用例中是否足够。根据我的理解,TPL的主要用例是将一个巨大的任务分成几个可以并行运行的小任务。例如,如果您有一个大的列表,并且您希望对每个元素应用相同的转换。在这种情况下,您可以在列表的子集上应用转换的几个任务。
您描述的案例似乎不适合我。在您的情况下,您没有几个并行执行相同操作的任务。你有几个不同的任务,每个任务都是自己的工作(生产者)和一个消耗的任务。如果你想拥有多个消费者,也许TPL可以用于消费者部分,因为在这种情况下,每个消费者都做同样的工作(假设你找到了一个逻辑来强制你寻找的时间一致性)。
嗯,这当然只是我对这个主题的个人看法
活得长久而繁荣
答案 3 :(得分:2)
听起来BlockingCollection对你来说很方便。因此,对于上面的代码,您可以使用类似的内容(假设_queue
是BlockingCollection
实例):
// for your producers
_queue.Add(some_work);
处理队列的专用工作线程:
foreach (var some_work in _queue.GetConsumingEnumerable())
{
deal_with(some_work);
}
注意:当所有制作人都完成制作时,您需要在CompleteAdding()
上致电_queue
,否则您的消费者将无法等待更多工作。