【问题标题】:Redis in a threaded environment is inconsistentRedis在线程环境下不一致
【发布时间】:2023-03-28 15:46:01
【问题描述】:

我在线程环境中运行时遇到了一些 redis 问题。

我有一个名为 AwaitableParallelForeachWorker 的类,我可以在其中为有效负载中的每个项目运行特定的函数。 (我知道它不漂亮,但它确实有效)

 public class AwaitableParallelForeachWorker : IAwaitableParallelForeachWorker
 {
    private readonly object _lockObject = new object();
    private int _tasksCompleted;

    public async Task Run<T>(Func<T, Task> action, IEnumerable<T> payload)
    {
        var list = payload.ToList();
        var tasks = list.Select(x => new Task(async () =>
        {
            await action(x);
            TaskDone();
        }));
        Parallel.ForEach(tasks, task => task.Start());
        while (_tasksCompleted < list.Count)
        {
            await Task.Delay(10);
        }
    }

    public void TaskDone()
    {
        lock (_lockObject)
        {
            _tasksCompleted++;
        }
    }
}

这里是redis缓存代码:

 public class NewsappRedisCache : INewsappRedisCache
 {
    private static readonly string ConnectionString = ConfigurationManager.AppSettings["RedisCache"];

    private static readonly Lazy<ConnectionMultiplexer> LazyConnection =
        new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(ConnectionString));

    private static IDatabase MimerArticleDatabase => Connection.GetDatabase(2);

    public async Task<MimerArticle> GetMimerArticleAsync(Guid id)
    {
        var redisValue = await MimerArticleDatabase.StringGetAsync($"{nameof(MimerArticle)}-{id}");
        if (!redisValue.HasValue) return null;
        var mimerArticle = JsonConvert.DeserializeObject<MimerArticle>(redisValue.ToString());
        return mimerArticle;
    } 

我编写了这个简单的测试,它使用 AwaitableParallelForeachWorker 调用我的 redis 缓存 1000 次

  public class Test
  {
    private NewsappRedisCache _redisCache;

    [Fact]
    public async void TestRedis()
    {
        var guids = new List<Guid>();

        for (var i = 0; i < 1000; i++)
        {
            guids.Add(Guid.NewGuid());
        }

        _redisCache = new NewsappRedisCache();
        await new AwaitableParallelForeachWorker().Run(CallRedis, guids);
    }

    private async Task CallRedis(Guid id)
    {
        await _redisCache.GetMimerArticleAsync(id);
    }
}

现在变得很奇怪。有时 1000 次访问 redis 缓存会在一瞬间执行。我已经通过检查 azure 门户验证了 get 确实命中了缓存。但有时每次获取大约需要 1 秒。

我不知道为什么。我尝试更改 AwaitableParallelForeachWorker 的功能,但它一直不一致。 如果我在正常的 foreach 中运行每个 get,它执行得很好,但不如 AwaitableParallelForeachWorker 实际工作时那么快。

所以我一直认为它与线程/任务有关。

谁能提供一些帮助?

【问题讨论】:

    标签: c# multithreading azure redis


    【解决方案1】:

    我找不到确切的原因,但我也无法将其放在评论中,因此需要考虑以下几点:

    • 测试方法不应该是async Task,而不是async void吗?一些测试框架处理这种可以说是不正确的用法,但也许你的没有。如果框架无法知道方法何时完成,它会认为一切都在第一次等待时完成。

    • 您的Run() 方法可以显着简化为:

      public Task Run<T>(Func<T, Task> action, IEnumerable<T> payload)
      {
          return Task.WhenAll(payload.Select(action));
      }
      

      然后摆脱其余的班级。

    • 您的CallRedis() 方法不需要状态机开销;而是:

      private Task CallRedis(Guid id)
      {
          return _redisCache.GetMimerArticleAsync(id);
      }
      
    • 虽然我正在为您节省台词:

      var guids = Enumerable.Range(0, 1000).Select(Guid.NewGuid()).ToList();
      // :)
      

    【讨论】:

    • 对 Task.WhenAll(payload.Select(action)) 的更改起到了作用!我以前使用过:return Task.WhenAll(payload.Select(async x => await action(x))),但这与我制作的奇怪的 Run 方法相同。这就是为什么我最终以这种方式实现 Run ......我显然还没有得到 async 和 await 。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2013-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-08-31
    相关资源
    最近更新 更多