Home > database >  Is there a built-in limited-length wrapped-list (end-wraps-to-start) where you can only access the l
Is there a built-in limited-length wrapped-list (end-wraps-to-start) where you can only access the l

Time:07-23

I need to keep elements in a list/array, so that I can access the last n elements. I.e. I add to it all the time, and should be able to get from it the last elements if needed. (Like a security camera only keeping the last n hours.) I was thinking of having a List or an array, and maintaining an index where the index wraps around.

But this seems like a useful construction so I was wondering if it already exists in .Net. No need to reinvent the wheel if it does.

So does such a thing exist built in to .Net?

EDIT

Apparently, there is a question of how to implement this here. That question and top answers are from more than 10 years ago. That does not answer my question of whether it is built-in today. A lot of what we use today didn't exist then. Also, using Queue won't help because I need to be able to access every element (up to n), not only the last one.

CodePudding user response:

This describes a circular or ring buffer. Implementing one is relatively easy and googling or searching NuGet returns many implementations, including the CircularQueue in the C5 library.

For asynchronous operations, one can use a BoundedChannel configured with BoundedChannelFullMode.DropOldest:

var options=new BoundedChannelOptions 
    { 
        Capacity = 10,
        FullMode = BoundedChannelFullMode.DropOldest
    };
var channel=Channel.CreateBounded<float>(options);

var writer=channel.Writer;
while(!cancellationToken.IsCancellationRequested)
{
   var value=await getSomeValue(cancellationToken);
   await writer.WriteAsync(value,cancellationToken);
}
writer.Complete();

Channels are specialized asynchronous queues for producer/consumer scenarios so their API and behavior is quite different from normal queues.

  • Writers and readers (producers and consumers) use different APIs to write (ChannelWriter and read ChannelReader. The Channel class itself has no useful members beyond the Reader and Writer properties
  • Writing and reading are explicitly asynchronous.
  • Both write and read order are preserved
  • Channels can be unbounded or bounded. An unbounded channel can grow infinitely, which can be a problem when readers are slower than writers. With Bounded channels, once a channel reaches its upper bound it can block writers (asynchronously) or discard the oldest or newest item.

Channels are used eg in SignalR server-to-client streaming to produce server events. The doc example uses an unbounded owned channel, ie a channel whose lifetime is controlled by the producer method. That could easily be a bounded channel with DropOldest. This would be useful if we want to only send "fresh" events to potentially slow subscribers :

public ChannelReader<int> Counter(
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    var options=new BoundedChannelOptions 
    { 
        Capacity = 10,
        FullMode = BoundedChannelFullMode.DropOldest
    };
    var channel=Channel.CreateBounded<int>(options);


    // We don't want to await WriteItemsAsync, otherwise we'd end up waiting 
    // for all the items to be written before returning the channel back to
    // the client.
    _ = WriteItemsAsync(channel.Writer, count, delay, cancellationToken);

    return channel.Reader;
}

private async Task WriteItemsAsync(
    ChannelWriter<int> writer,
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    Exception localException = null;
    try
    {
        for (var i = 0; i < count; i  )
        {
            await writer.WriteAsync(i, cancellationToken);

            // Use the cancellationToken in other APIs that accept cancellation
            // tokens so the cancellation can flow down to them.
            await Task.Delay(delay, cancellationToken);
        }
    }
    catch (Exception ex)
    {
        localException = ex;
    }
    finally
    {
        writer.Complete(localException);
    }
}

If we set capacity to 1 we get Publish Latest behavior :

public ChannelReader<int> CounterLatest(
    int count,
    int delay,
    CancellationToken cancellationToken)
{
    var options=new BoundedChannelOptions 
    { 
        Capacity = 1,
        FullMode = BoundedChannelFullMode.DropOldest
    };
    var channel=Channel.CreateBounded<int>(options);


    // We don't want to await WriteItemsAsync, otherwise we'd end up waiting 
    // for all the items to be written before returning the channel back to
    // the client.
    _ = WriteItemsAsync(channel.Writer, count, delay, cancellationToken);

    return channel.Reader;
}

A consumer uses the ChannelReader and eg the IAsyncEnumerable<>returned byChannelReader.ReadAllAsync` to read messages:

async Task ConsumeAsync(ChannelReader<int> input,CancellationToken cancellationToken)
{
    await foreach(var msg in input.ReadAllAsync(cancellationToken))
    {
        await DoSomething(msg);
    }
}
  • Related