Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 生产者消费者多生产者多队列单消费者_C#_Multithreading_Asynchronous_Concurrency_Producer Consumer - Fatal编程技术网

C# 生产者消费者多生产者多队列单消费者

C# 生产者消费者多生产者多队列单消费者,c#,multithreading,asynchronous,concurrency,producer-consumer,C#,Multithreading,Asynchronous,Concurrency,Producer Consumer,我们有一个一般的日志记录问题,我们做3种类型的日志记录(比如说:跟踪、审计、计数)是我们做日志记录的3个方面。我们同时执行这些操作,从REST web服务中运行的代码,到传入的每个web请求 对于每个请求,我们都会对每个日志记录区域进行多次调用。比如说,每个web请求、一些跟踪、一些审计、一些计数器平均总共大约有100个日志调用,每个web请求总共有100个调用。每次日志调用都会将一些数据写入某个存储以供以后处理[存储是什么并不重要,但将其放入该存储肯定是受I/O限制的(实际上它是一个azure

我们有一个一般的日志记录问题,我们做3种类型的日志记录(比如说:跟踪、审计、计数)是我们做日志记录的3个方面。我们同时执行这些操作,从REST web服务中运行的代码,到传入的每个web请求

对于每个请求,我们都会对每个日志记录区域进行多次调用。比如说,每个web请求、一些跟踪、一些审计、一些计数器平均总共大约有100个日志调用,每个web请求总共有100个调用。每次日志调用都会将一些数据写入某个存储以供以后处理[存储是什么并不重要,但将其放入该存储肯定是受I/O限制的(实际上它是一个azure队列,因此是跨interweb的HTTP调用)]

我们的问题是,记录任何信息(在这三个区域中的任何一个区域)的行为需要服务请求线程太长的时间来写入所有日志接收器100次。我们测量到,多达四分之五的请求处理时间用于写入日志接收器!因此,您可以看到我们需要对其进行显著优化

我们希望加快速度,并将请求线程从日志写入中解放出来。我们希望使用另一个线程在后台以自己的速度将日志数据写入3个日志存储。因此,我们认为在内存队列中对日志写入进行排队,以便日志写入线程能够在多个请求进入时并行处理它们,这是一种可行的方法

Stephen Cleary的书《C#Cookbook中的并发》(fab reference)指出,在这种情况下,使用像
BlockingCollection
这样的阻塞集合是理想的。这样,一次一个线程可以生成数据(并写入内存队列),另一个线程可以使用内存队列中的数据。看起来这对我们的情况很理想

然而,在我们的例子中,因为我们在ASP.NET主机上运行,并且线程池线程对于web服务器非常宝贵(为了可伸缩性),所以我们只希望有一个线程专用于[消耗]所有3个日志队列。让所有其他线程处理入站web请求,并[生成]日志数据

因此问题变成了:如何使用3个
BlockingCollection
实例(每种日志类型一个),并支持:

  • 用于生成数据的任意数量的生产者[入站web请求线程]
  • 一个单一(专用)日志线程使用者,始终持续高效地使用所有三个队列

  • 有人能想到一种设计模式在这里很有效吗?我们缺少的一点是,消费者如何有效地清空所有三个队列,而不阻塞其中任何一个队列,并持续处理所有三个队列。

    以下是我在类似场景中成功使用的模式

    首先,我通常使用
    ConcurrentQueue
    ,因为没有任何需要阻止的场景。这可能会因日志记录量的不同而有所不同。队列包含在
    缓冲区
    类中。它完全可以包含多个队列

    请求线程所做的就是将项目放入队列中。在同一个线程上,
    缓冲区
    根据最大大小检查数量,如果确定需要刷新缓冲区,则在单独的线程上执行此操作。还有一个计时器,这样即使缓冲区未满,也会刷新缓冲区。无论哪种情况,都会进行检查以确保缓冲区尚未刷新

    其结果是,除非缓冲区永久充满,否则不需要总是有一个线程专用于日志,在没有消息时阻塞


    您还可以通过成批而不是单独向队列发送消息来提高性能。通过发送一条包含50-100条消息的“消息”,而不是单独发送每一条消息,可以减少大量开销。

    您可以有一个
    BlockingCollection
    ,其中
    T
    是一些自定义类,可以包含这三种类型中的任何一种。您可以在该类型中使用枚举来检测该类真正包含的类型。谢谢,但从您的回答中,我看不出我们如何使用相同的使用者线程连续处理所有三个
    BlockingCollection
    队列,而不必等待其中任何一个队列。我建议您使用单个
    BlockingCollection
    ,不是三个,我现在明白了。谢谢你的澄清。枚举描述数据,最终帮助消费者决定将其添加到哪个存储。是的,可以。谢谢@Scott,这里有一些好主意,你建议的一些优化在我们的场景中有一定的意义。我特别喜欢添加超时和最大阈值。您是否建议我们使用Task.Run()启动日志线程(从请求线程启动)?是的。我有一个通用的实现,但我找不到它,所以我再次编写它。其思想是一个通用的
    ISink
    接口,
    BufferedSink
    是一个实现。它又包含另一个它写入的
    ISink
    。它还取决于
    IBufferedSinkSettings
    接口,该接口包含最大缓冲区大小、计时器间隔,还可以指定对内部缓冲区的刷新是否应同步。