使用net6.0的memory实现Redis的入队出队功能
nuget包:
Microsoft.Extensions.Caching.Memory
Microsoft.Extensions.Configuration
Microsoft.Extensions.DependencyInjection
cs
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
using System.Collections.Concurrent;
public interface IMemoryCacheListService
{
/// <summary>
/// 在列表的尾部(右侧)添加一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
Task<long> RPushAsync<T>(string key, T value, TimeSpan? expiry = null);
/// <summary>
/// 在列表的头部(左侧)添加一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
Task<long> LPushAsync<T>(string key, T value, TimeSpan? expiry = null);
/// <summary>
/// 移除并返回列表头部(左侧)的第一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
Task<T> LPopAsync<T>(string key);
/// <summary>
/// 移除并返回列表尾部(右侧)的最后一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
Task<T> RPopAsync<T>(string key);
/// <summary>
/// 返回列表中指定范围内的元素。范围由起始索引(start)和结束索引(stop)指定,支持负数索引(表示从尾部开始计算)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="start"></param>
/// <param name="stop"></param>
/// <returns></returns>
Task<List<T>> LRangeAsync<T>(string key, int start, int stop);
/// <summary>
/// 返回列表的长度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
Task<long> LLenAsync<T>(string key);
/// <summary>
/// 通过索引获取列表中的元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns></returns>
Task<T> LIndexAsync<T>(string key, int index);
/// <summary>
/// 为列表设置过期时间
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="expiry"></param>
/// <returns></returns>
Task<bool> ExpireAsync<T>(string key, TimeSpan expiry);
/// <summary>
/// 修剪列表,只保留指定范围内的元素,其他元素将被删除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="start"></param>
/// <param name="stop"></param>
/// <returns></returns>
Task<bool> LTrimAsync<T>(string key, int start, int stop);
/// <summary>
/// 移除列表
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
Task<bool> RemoveAsync<T>(string key);
/// <summary>
/// 获取列表中的所有元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
IAsyncEnumerable<T> GetItemsAsync<T>(string key);
}
public class MemoryCacheListService : IMemoryCacheListService, IDisposable
{
private readonly IMemoryCache _memoryCache;
private readonly IOptions<CustomMemoryCacheOptions> _options;
private readonly ConcurrentDictionary<string, SemaphoreSlim> _semaphores = new();
public MemoryCacheListService(
IMemoryCache memoryCache,
IOptions<CustomMemoryCacheOptions> options = null)
{
_memoryCache = memoryCache;
_options = options;
}
/// <summary>
/// 在列表的尾部(右侧)添加一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
public async Task<long> RPushAsync<T>(string key, T value, TimeSpan? expiry = null)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
list = new List<T>();
}
list.Add(value);
SetListWithExpiry(cacheKey, list, expiry);
return list.Count;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 在列表的头部(左侧)添加一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expiry"></param>
/// <returns></returns>
public async Task<long> LPushAsync<T>(string key, T value, TimeSpan? expiry = null)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
list = new List<T>();
}
list.Insert(0, value);
SetListWithExpiry(cacheKey, list, expiry);
return list.Count;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 移除并返回列表头部(左侧)的第一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> LPopAsync<T>(string key)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null || list.Count == 0)
{
return default(T);
}
var value = list[0];
list.RemoveAt(0);
if (list.Count > 0)
{
SetListWithExpiry(cacheKey, list);
}
else
{
_memoryCache.Remove(cacheKey);
}
return value;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 移除并返回列表尾部(右侧)的最后一个元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<T> RPopAsync<T>(string key)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null || list.Count == 0)
{
return default(T);
}
// 修复:正确获取最后一个元素
var value = list[list.Count - 1]; // 正确的索引访问
list.RemoveAt(list.Count - 1);
if (list.Count > 0)
{
SetListWithExpiry(cacheKey, list);
}
else
{
_memoryCache.Remove(cacheKey);
}
return value;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 返回列表中指定范围内的元素。范围由起始索引(start)和结束索引(stop)指定,支持负数索引(表示从尾部开始计算)
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="start"></param>
/// <param name="stop"></param>
/// <returns></returns>
public async Task<List<T>> LRangeAsync<T>(string key, int start, int stop)
{
var cacheKey = GetCacheKey(key);
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
return new List<T>();
}
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
var normalizedStart = NormalizeIndex(start, list.Count);
var normalizedStop = NormalizeIndex(stop, list.Count);
normalizedStart = Math.Max(0, normalizedStart);
normalizedStop = Math.Min(list.Count - 1, normalizedStop);
if (normalizedStart > normalizedStop)
return new List<T>();
return list.GetRange(normalizedStart, normalizedStop - normalizedStart + 1);
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 返回列表的长度
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<long> LLenAsync<T>(string key)
{
var cacheKey = GetCacheKey(key);
if (_memoryCache.TryGetValue(cacheKey, out List<T> list) && list != null)
{
return list.Count;
}
return 0;
}
/// <summary>
/// 通过索引获取列表中的元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns></returns>
public async Task<T> LIndexAsync<T>(string key, int index)
{
var cacheKey = GetCacheKey(key);
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null || list.Count == 0)
{
return default(T);
}
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
var normalizedIndex = NormalizeIndex(index, list.Count);
if (normalizedIndex < 0 || normalizedIndex >= list.Count)
return default(T);
return list[normalizedIndex];
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 为列表设置过期时间
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="expiry"></param>
/// <returns></returns>
public async Task<bool> ExpireAsync<T>(string key, TimeSpan expiry)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
return false;
}
SetListWithExpiry(cacheKey, list, expiry);
return true;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 修剪列表,只保留指定范围内的元素,其他元素将被删除
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="start"></param>
/// <param name="stop"></param>
/// <returns></returns>
public async Task<bool> LTrimAsync<T>(string key, int start, int stop)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
return false;
}
var normalizedStart = NormalizeIndex(start, list.Count);
var normalizedStop = NormalizeIndex(stop, list.Count);
normalizedStart = Math.Max(0, normalizedStart);
normalizedStop = Math.Min(list.Count - 1, normalizedStop);
if (normalizedStart > normalizedStop)
{
list.Clear();
}
else
{
var newList = list.GetRange(normalizedStart, normalizedStop - normalizedStart + 1);
list.Clear();
list.AddRange(newList);
}
if (list.Count > 0)
{
SetListWithExpiry(cacheKey, list);
}
else
{
_memoryCache.Remove(cacheKey);
}
return true;
}
finally
{
semaphore.Release();
}
}
/// <summary>
/// 移除列表
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async Task<bool> RemoveAsync<T>(string key)
{
var cacheKey = GetCacheKey(key);
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
_memoryCache.Remove(cacheKey);
return true;
}
finally
{
semaphore.Release();
_semaphores.TryRemove(cacheKey, out _);
}
}
/// <summary>
/// 获取列表中的所有元素
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public async IAsyncEnumerable<T> GetItemsAsync<T>(string key)
{
var cacheKey = GetCacheKey(key);
if (!_memoryCache.TryGetValue(cacheKey, out List<T> list) || list == null)
{
yield break;
}
var semaphore = _semaphores.GetOrAdd(cacheKey, _ => new SemaphoreSlim(1, 1));
await semaphore.WaitAsync();
try
{
foreach (var item in list)
{
yield return item;
}
}
finally
{
semaphore.Release();
}
}
private string GetCacheKey(string key) => $"list_{key}";
private void SetListWithExpiry<T>(string cacheKey, List<T> list, TimeSpan? expiry = null)
{
var cacheOptions = new MemoryCacheEntryOptions();
if (expiry.HasValue)
{
cacheOptions.AbsoluteExpirationRelativeToNow = expiry;
}
else
{
cacheOptions.SlidingExpiration = _options?.Value.DefaultSlidingExpiration;
cacheOptions.AbsoluteExpirationRelativeToNow = _options?.Value.DefaultAbsoluteExpiration;
}
_memoryCache.Set(cacheKey, list, cacheOptions);
}
private int NormalizeIndex(int index, int count)
{
return index < 0 ? count + index : index;
}
public void Dispose()
{
foreach (var semaphore in _semaphores.Values)
{
semaphore.Dispose();
}
_semaphores.Clear();
}
}
public class CustomMemoryCacheOptions
{
/// <summary>
/// 相对过期时间(滑动过期时间)
/// </summary>
public TimeSpan? DefaultSlidingExpiration { get; set; } = TimeSpan.FromMinutes(30);
/// <summary>
/// 绝对过期时间
/// </summary>
public TimeSpan? DefaultAbsoluteExpiration { get; set; } = TimeSpan.FromHours(1);
}
Sample:
cs
var key = "key";
var services = new ServiceCollection();// 创建ServiceCollection
// 注册缓存服务
services.Configure<CustomMemoryCacheOptions>(options =>
{
options.DefaultSlidingExpiration = TimeSpan.FromMinutes(30);
options.DefaultAbsoluteExpiration = TimeSpan.FromHours(2);
});
services.AddTransient<IMemoryCacheListService, MemoryCacheListService>();
services.AddMemoryCache();
var serviceProvider = services.BuildServiceProvider();
var stackService = serviceProvider.GetRequiredService<IMemoryCacheListService>();
#region Queue 先进先出
Console.WriteLine($"Queue 先进先出");
await stackService.RPushAsync(key, "item1");
await stackService.RPushAsync(key, "item2");
await stackService.RPushAsync(key, "item3");
while (true)
{
string value = await stackService.LPopAsync<string>(key);
if (value == null)
break;
Console.WriteLine($"Time:{DateTime.Now:yyyy-MM-dd HH:mm:ss} Message: {value}");
await Task.Delay(1000);
}
#endregion
#region Stack 先进后出
Console.WriteLine($"Stack 先进后出");
await stackService.RPushAsync(key, "item1");
await stackService.RPushAsync(key, "item2");
await stackService.RPushAsync(key, "item3");
while (true)
{
string value = await stackService.RPopAsync<string>(key);
if (value == null)
break;
Console.WriteLine($"Time:{DateTime.Now:yyyy-MM-dd HH:mm:ss} Message: {value}");
await Task.Delay(1000);
}
#endregion
Console.ReadKey();
output:
