C# .NET - Buffer messages w/Timer

元气小坏坏 提交于 2019-12-02 05:46:25

There is a fantastic library for these kind of requirements (combine time with sequences), it is Reactive Extensions. See https://github.com/Reactive-Extensions/Rx.NET

You could then write something like

void Main()
{
    messages
        .Buffer(TimeSpan.FromMinutes(1), 100) // Buffer until 100 items or 1 minute has elapsed, whatever comes first.
        .Subscribe(msgs => SendMessages(msgs));     
}

Subject<Message> messages = new Subject<Message>();

public void GotNewMessage(Message msg)
{
    messages.OnNext(msg);
}

Note: this is not production ready but it shows the basic of how to do it. Depending on where you het the messages from there are better ways to create an Observable to subscribe to.

More references:

If your message are received using an event you can link the event to a RX stream, see https://msdn.microsoft.com/en-us/library/hh242978(v=vs.103).aspx and https://msdn.microsoft.com/en-us/library/system.reactive.linq.observable.fromeventpattern(v=vs.103).aspx

First of all, you should consider using a ConcurrentQueue<> insted of a List<>. ConcurrentQueue<> is all the way thread safe and needs no additional locks. With this, you have already spared yourself a lock for the message queue. Interlocked provides atomicity, when it's not available.

According to the C# language specification, independent reads/writes are atomic (but only for some data types and long is not always atomic - that's why I shifted the DateTime.Now.Ticks to get an int32 without losing any bits that would influence the elapsed time) and read-modify-write (eg. ++i) is never atomic.

Shifting (eg. <<) is on its own atomic and doesn't need any additional locking.

private ConcurrentQueue<Message> Queue = new ConcurrentQueue<Message>();
private int QueueSize = 0;
private int LastSend = (int)(DateTime.Now.Ticks >> 23);
private int LastMessage = (int)(DateTime.Now.Ticks >> 23);

public void GotNewMessage(Message Message)
{
    Queue.Enqueue(Message);

    Interlocked.Increment(ref QueueSize);
    Interlocked.Exchange(ref LastMessage, (int)(DateTime.Now.Ticks >> 23));

    if (Interlocked.CompareExchange(ref QueueSize, 0, 100) >= 100 || 
        LastMessage - LastSend >= 60)
    {
        Message Dummy;
        while (!Queue.IsEmpty)
            if (Queue.TryDequeue(out Dummy))
                SendMessage(Dummy);

        Interlocked.Exchange(ref LastSend, (int)(DateTime.Now.Ticks >> 23));
    }
}

public void SendMessage(Message Message)
{
    // ...
}

Edit: It may occur, that more than 100 messages are sent out. If you wish to send out strictly 100 messages, you can implement an another atomic incrementation in the cycle.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!