从ASP.net项目调用静态异步方法

hyp*_*erN 7 c# asp.net

我想知道这个场景是否是线程安全的,是否存在我目前没有看到的问题:

  1. 从ASP.net控制器我从非静态类调用非静态方法(此类在另一个项目中,类被注入到控制器中).

  2. 这个方法(非静态的)做了一些工作,并调用一些其他静态方法传递userId

  3. 最后,静态方法做了一些工作(需要userId)

我相信这种方法是线程安全的,并且如果两个用户同时调用此方法,那么一切都将正确完成(让我们说同样的纳秒).我是正确还是完全错误?如果我错了,在ASP.net项目中使用静态方法的正确方法是什么?

编辑

这是代码:)

这是来自控制器的调用:

await _workoutService.DeleteWorkoutByIdAsync(AzureRedisFeedsConnectionMultiplexer.GetRedisDatabase(),AzureRedisLeaderBoardConnectionMultiplexer.GetRedisDatabase(), workout.Id, userId);
Run Code Online (Sandbox Code Playgroud)

这里DeleteWorkoutByIdAsync的样子如下:

public async Task<bool> DeleteWorkoutByIdAsync(IDatabase redisDb,IDatabase redisLeaderBoardDb, Guid id, string userId)
    {

        using (var databaseContext = new DatabaseContext())
        {
            var workout = await databaseContext.Trenings.FindAsync(id);

            if (workout == null)
            {
                return false;
            }

            databaseContext.Trenings.Remove(workout);

            await databaseContext.SaveChangesAsync();

            await RedisFeedService.StaticDeleteFeedItemFromFeedsAsync(redisDb,redisLeaderBoardDb, userId, workout.TreningId.ToString());
        }

        return true;
    }
Run Code Online (Sandbox Code Playgroud)

您可以注意到DeleteWorkoutByIdAsync调用静态方法StaticDeleteFeedItemFromFeedsAsync,如下所示:

public static async Task StaticDeleteFeedItemFromFeedsAsync(IDatabase redisDb,IDatabase redisLeaderBoardDd, string userId, string workoutId)
 {


        var deleteTasks = new List<Task>();
        var feedAllRedisVals = await redisDb.ListRangeAsync("FeedAllWorkouts:" + userId);
        DeleteItemFromRedisAsync(redisDb, feedAllRedisVals, "FeedAllWorkouts:" + userId, workoutId, ref deleteTasks);


        await Task.WhenAll(deleteTasks);
  }
Run Code Online (Sandbox Code Playgroud)

这里是静态方法DeleteItemFromRedisAsync,它在StaticDeleteFeedItemFromFeedsAsync中调用:

private static void DeleteItemFromRedisAsync(IDatabase redisDb, RedisValue [] feed, string redisKey, string workoutId, ref List<Task> deleteTasks)
  {
        var itemToRemove = "";

        foreach (var f in feed)
        {

            if (f.ToString().Contains(workoutId))
            {
                itemToRemove = f;
                break;
            }

        }
        if (!string.IsNullOrEmpty(itemToRemove))
        {
            deleteTasks.Add(redisDb.ListRemoveAsync(redisKey, itemToRemove));
        }
  }
Run Code Online (Sandbox Code Playgroud)

Eam*_*nne 3

“线程安全”不是一个独立的术语。线程安全面临着什么?您期望在这里进行什么样的并发修改?

这里我们从几个方面来看:

  • 您自己的可变共享状态:在此代码中您没有任何共享状态;所以它自动是线程安全的。
  • 间接共享状态:DatabaseContext. 这看起来像一个 SQL 数据库,并且这些数据库往往是线程“安全”的,但这到底意味着什么取决于所讨论的数据库。例如,您要删除一行Trenings,如果其他线程也删除同一行,您可能会收到(安全)并发冲突异常。根据隔离级别,即使对于“Trenings”的其他某些突变,您也可能会遇到并发冲突异常。在最坏的情况下,这意味着一个请求失败,但数据库本身不会损坏。
  • Redis 本质上是单线程的,因此所有操作都是序列化的,从这个意义上说是“线程安全的”(这可能不会给你带来太多好处)。您的删除代码获取一组键,然后最多删除其中一个。如果两个或多个线程同时尝试删除同一个键,则一个线程可能会尝试删除一个不存在的键,这可能是您意想不到的(但它不会导致数据库损坏)。
  • redis+sql 之间的隐式一致性:看起来您正在使用 guid,因此不相关的事物发生冲突的可能性很小。您的示例仅包含删除操作(这可能不会导致一致性问题),因此很难推测在所有其他情况下 redis 和 sql 数据库是否会保持一致。一般来说,如果您的 ID 从未被重复使用,那么您可能是安全的 - 但保持两个数据库同步是一个难题,而且您很可能在某个地方犯错误。

但是,您的代码对于其正在执行的操作来说似乎过于复杂。如果您希望能够长期保持这一点,我建议您大幅简化它。

  • 除非您确实知道自己在做什么,否则不要使用ref参数(这里没有必要)。
  • 不要将字符串与其他数据类型混淆,因此请ToString()尽可能避免。 绝对要避免一些令人讨厌的技巧,例如Contains检查键是否相等。您希望代码在发生意外情况时中断,因为“一瘸一拐”的代码几乎不可能调试(并且您编写错误)。
  • 如果您真正能做的唯一一件事就是等待所有任务,则不要有效地返回一组任务 - 最好在被调用方中执行此操作以简化 API。
  • 不要使用redis。这可能只是一种干扰 - 您已经有了另一个数据库,因此您不太可能在这里需要它,除非出于性能原因,并且为假设的性能问题添加整个额外的数据库引擎还为时过早。很有可能,需要额外连接的额外开销可能会使您的代码比只有一个数据库时更慢,尤其是在您无法保存许多 sql 查询的情况下。