简体   繁体   English

用于从 BlockingCollection 消费的计时器 vs 线程 vs RegisteredWaitHandle

[英]Timer vs Thread vs RegisteredWaitHandle for consuming from BlockingCollection

I have a service which talks to around 50 devices over Ethernet using TCP.我有一项服务,它使用 TCP 通过以太网与大约 50 个设备通信。 These devices push data at various speeds (ranging from 50ms to 1500ms).这些设备以不同的速度(从 50 毫秒到 1500 毫秒)推送数据。 Hence I use BlockingCollection (per device) for queuing the received data and then processing the data in a different thread.因此,我使用 BlockingCollection(每个设备)对接收到的数据进行排队,然后在不同的线程中处理数据。

To consume and process the data from the BlockingCollection, I used an explicit thread per device as shown below为了使用和处理 BlockingCollection 中的数据,我为每个设备使用了一个显式线程,如下所示

private void ThreadProc(object o)
{
    while (true)
    {
        DeviceData data = m_blockingCollection.Take();
        ProcessData(data);
    }
}

IMHO, this is a simple and elegant solution since these threads will be blocked if there is no data available and will not consume CPU.恕我直言,这是一个简单而优雅的解决方案,因为如果没有可用数据,这些线程将被阻塞并且不会消耗 CPU。

One alternate approach (recommended by colleagues) is a timer with an interval of 250ms.一种替代方法(由同事推荐)是间隔为 250 毫秒的计时器。 But I have a gut feeling that scheduling around 50 operations every 250 milliseconds might be costlier.但我有一种直觉,每 250 毫秒安排大约 50 次操作可能会更昂贵。 For faster devices this will slow down processing and for slower devices this will cause unnecessary execution of the timer logic.对于较快的设备,这将减慢处理速度,对于较慢的设备,这将导致定时器逻辑的不必要执行。

private void OnTimeOut(object o)
{
    if (m_blockingCollection.TryTake(out DeviceData data))
    {
        ProcessData(data);
    }
}

On researching about this, I also found about ThreadPool.RegisterWaitForSingleObject which looks apt to the problem.在对此进行研究时,我还发现了 ThreadPool.RegisterWaitForSingleObject 看起来很适合这个问题。 Hence I modified the code as below因此我修改了代码如下

AutoResetEvent m_dataEvent = new AutoResetEvent(false);
RegisteredWaitHandle reg = ThreadPool.RegisterWaitForSingleObject
                         (m_dataEvent, ConsumeAndProcess, null, -1, false);

    private void OnDeviceDataRecevied(DeviceData data)
    {
        sm_blockingCollection.Add(data);
        m_dataEvent.Set();
    }

    private static void ConsumeAndProcess(object state, bool timedOut)
    {
        if (sm_blockingCollection.TryTake(out int data))
        {
            ProcessData(data);
        }
    }

Did I think correctly?我想对了吗? Is the 3rd approach better than the first and second?第三种方法比第一种和第二种方法更好吗? Which will be better in terms of efficiency and in terms of less resource usage?在效率和更少的资源使用方面哪个会更好?

Going from a BlockingCollection to a Timer looks like a technological regression to me.BlockingCollectionTimer对我来说似乎是一种技术倒退。 And implementing a solution on top of AutoResetEvent looks like an attempt to re-implement a BlockingCollection , with a low probability of building a better one, and a significant probability of building a buggy one.AutoResetEvent之上实现一个解决方案看起来像是重新实现一个BlockingCollection的尝试,构建一个更好的解决方案的可能性很低,而构建一个有问题的解决方案的可能性很大。

Having 50 threads blocked most of the time is not that terrible (each thread consumes "only" 1MB of memory), but this is a low scalability setup.大多数时候有 50 个线程被阻塞并没有那么糟糕(每个线程“只消耗” 1MB内存),但这是一个低可伸缩性设置。 Fortunately now there are build-in tools available that allow to upgrade from a blocking collection to an async collection, and have the advantages of the BlockingCollection (responsiveness, low CPU utilization) without the disadvantages (blocked threads).幸运的是,现在有可用的内置工具允许从阻塞集合升级到异步集合,并且具有BlockingCollection的优点(响应能力、低 CPU 利用率)而没有缺点(阻塞线程)。 For example using Channels you could go from 50 devices to 5,000 devices, using possibly a smaller number of threads than you are currently using (shared ThreadPool threads instead of dedicated threads).例如,使用Channels ,您可以 go 从 50 个设备到 5,000 个设备,使用的线程数量可能比您当前使用的更少(共享ThreadPool线程而不是专用线程)。

private Channel<DeviceData> m_channel = Channel.CreateUnbounded<DeviceData>();

private async Task DeviceProcessorAsync()
{
    while (await m_channel.Reader.WaitToReadAsync())
    {
        while (m_channel.Reader.TryRead(out DeviceData data))
        {
            ProcessData(data);
        }
    }
}

The Channels are built-in the .NET Core, and available as a package for .NET Framework.通道内置于 .NET 核心中,可作为.NET框架的 package 使用。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM