Files
UnrealEngine/Engine/Source/Programs/Shared/EpicGames.Redis/Utility/RedisQueue.cs
2025-05-18 13:04:45 +08:00

89 lines
3.0 KiB
C#

// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using StackExchange.Redis;
namespace EpicGames.Redis.Utility
{
/// <summary>
/// Utility method for queue instances
/// </summary>
public static class RedisQueue
{
/// <summary>
/// Create a new queue instance
/// </summary>
/// <param name="multiplexer">Multiplexer for the connection</param>
/// <param name="queueKey">Key for the queue</param>
/// <param name="eventChannel">Channel to use for posting update notifications</param>
/// <param name="cancellationToken">Cancellation token for the operation</param>
public static async Task<RedisQueue<T>> CreateAsync<T>(IConnectionMultiplexer multiplexer, RedisKey queueKey, RedisChannel eventChannel, CancellationToken cancellationToken = default)
{
RedisEvent asyncEvent = await RedisEvent.CreateAsync(multiplexer, eventChannel, cancellationToken);
return new RedisQueue<T>(multiplexer, asyncEvent, new RedisListKey<T>(queueKey));
}
}
/// <summary>
/// Implements a waitable FIFO queue with Redis. Wraps a pub/sub channel and list, ensuring that each item is only popped from the queue once.
/// </summary>
/// <typeparam name="T">Type of item in the queue</typeparam>
public sealed class RedisQueue<T> : IAsyncDisposable
{
readonly IConnectionMultiplexer _multiplexer;
readonly RedisEvent _asyncEvent;
readonly RedisListKey<T> _listKey;
internal RedisQueue(IConnectionMultiplexer multiplexer, RedisEvent asyncEvent, RedisListKey<T> listKey)
{
_multiplexer = multiplexer;
_asyncEvent = asyncEvent;
_listKey = listKey;
}
/// <inheritdoc/>
public ValueTask DisposeAsync()
=> _asyncEvent.DisposeAsync();
/// <summary>
/// Push a new item onto the queue
/// </summary>
/// <param name="item">Item to add to the queue</param>
/// <param name="flags">Flags for the push operation</param>
public async Task PushAsync(T item, CommandFlags flags = CommandFlags.None)
{
IDatabase database = _multiplexer.GetDatabase();
await database.ListRightPushAsync(_listKey, item, flags: flags);
_asyncEvent.Pulse();
}
/// <summary>
/// Attempt to pop an item from the front of the queue. Returns the default value for the item if the queue is empty.
/// </summary>
public async Task<T> TryPopAsync(CancellationToken cancellationToken = default)
{
_ = cancellationToken; // Don't currently support cancellation due to potential of items being dropped
IDatabase database = _multiplexer.GetDatabase();
return await database.ListLeftPopAsync(_listKey);
}
/// <summary>
/// Wait for a new item to be pushed onto the queue
/// </summary>
/// <param name="cancellationToken">Cancellation token for the operation</param>
public async Task WaitForDataAsync(CancellationToken cancellationToken = default)
{
Task task = _asyncEvent.Task;
IDatabase database = _multiplexer.GetDatabase();
if (await database.ListLengthAsync(_listKey) == 0)
{
await task.WaitAsync(cancellationToken);
}
}
}
}