c# 使用Memory实现Redis入队出队功能

使用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:

相关推荐
程序员萌萌28 分钟前
Redis的缓存机制和淘汰策略详解
数据库·redis·缓存机制·淘汰策略
lzhdim1 小时前
SharpCompress:跨平台的 C# 压缩与解压库
开发语言·c#
~plus~3 小时前
.NET 8 C# 委托与事件实战教程
网络·c#·.net·.net 8·委托与事件·c#进阶
beyond谚语4 小时前
接口&抽象类
c#·接口隔离原则·抽象类
新手小新4 小时前
C#学习笔记1-在VS CODE部署C#开发环境
笔记·学习·c#
深蓝电商API7 小时前
Redis在海淘场景下的缓存策略设计
数据库·redis·缓存·海淘
杰克尼7 小时前
redis(day04-达人探店)
数据库·redis·缓存
rockey6277 小时前
AScript动态脚本多语言环境支持
sql·c#·.net·script·eval·function·动态脚本
ou.cs8 小时前
c# SemaphoreSlim保姆级教程
开发语言·网络·c#
龙侠九重天8 小时前
ML.NET 实战:快速构建分类模型
分类·数据挖掘·c#·.net