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

本文关键字:静态 异步方法 调用 项目 ASP net | 更新日期: 2023-09-27 18:25:28

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

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

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

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

我相信这种方法是线程安全的,如果两个用户同时调用这个方法(比方说在同一纳秒内),一切都会正常进行。我是对的还是完全错了?如果我错了,在ASP.net项目中使用静态方法的正确方法是什么?

编辑

这是代码:)

这是来自控制器的呼叫:

await _workoutService.DeleteWorkoutByIdAsync(AzureRedisFeedsConnectionMultiplexer.GetRedisDatabase(),AzureRedisLeaderBoardConnectionMultiplexer.GetRedisDatabase(), workout.Id, userId);

下面是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;
    }

正如您所注意到的,DeleteWorkoutByIdAsync调用静态方法StaticDeleteFeedItemFromFeedsAnc,如下所示:

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);
  }

这里是静态方法DeleteItemFromRedisAnc,它在StaticDeleteFeedItemFromFeedsAnc中调用:

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));
        }
  }

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

"线程安全"不是一个独立的术语。线程安全面对什么?您希望在这里进行什么样的并发修改?

让我们看看这里的几个方面:

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

然而,您的代码似乎过于复杂。如果你想长期保持这种状态,我建议你大幅简化它。

  • 除非你真的知道自己在做什么,否则不要使用ref参数(这里没有必要)
  • 不要将字符串与其他数据类型混淆,因此尽可能避免使用ToString()绝对避免像Contains这样检查密钥相等性的讨厌技巧。当发生意外时,您希望您的代码中断,因为"一瘸一拐"的代码几乎不可能调试(并且您将编写错误)
  • 如果你唯一能做的就是等待所有任务,那么就不要有效地返回一组任务——最好在被调用者中这样做,以简化API
  • 不要使用redis。这可能只是分散了你的注意力——你已经有了另一个数据库,所以除了性能原因之外,你不太可能在这里需要它,而且现在为假设的性能问题添加整个额外的数据库引擎还为时过早。需要额外连接的额外开销可能会使代码比只有一个数据库时慢,尤其是在无法保存许多sql查询的情况下

注意:这个答案是在OP修改他们的问题以添加他们的代码之前发布的,这表明这实际上是一个async/await是否线程安全的问题。


静态方法本身并不是问题。如果一个静态方法是自包含的,并且只使用局部变量来完成它的工作,那么它是完全线程安全的。

如果静态方法不是自包含的(委托给线程不安全的代码),或者如果它以非线程安全的方式操纵静态状态,即访问lock()子句之外的静态变量进行读写,则会出现问题。

例如,int.parse()int.tryParse()是静态的,但完全是线程安全的。想象一下,如果它们不安全的话会有多恐怖。

您在这里所做的是在列表上同步(deleteTasks)。如果你这样做,我会推荐两件事中的一件。

1) 使用线程安全集合https://msdn.microsoft.com/en-us/library/dd997305(v=vs.110).aspx

2) 让DeleteItemFromRedisAsync返回一个任务并等待它。

尽管我认为在这种特殊情况下,只要重构它,我就看不到任何问题,而且DeleteItemFromRedisAsync可以被并行调用多次,那么你就会遇到问题。原因是,如果多个线程可以修改你的deleteTasks列表,那么你就不能再保证收集所有的deleteTask(https://msdn.microsoft.com/en-us/library/dd997373(v=vs.110).aspx如果两个线程同时以非线程安全的方式向末尾执行"添加"/"添加"操作,则其中一个线程将丢失),因此在等待所有线程完成时,您可能会错过一项任务。

此外,我会避免混合范式。要么使用async/await,要么跟踪任务集合并让方法添加到该列表中。不要两者都做。从长远来看,这将有助于代码的可维护性。(注意,线程仍然可以返回一个任务,您可以收集这些任务,然后等待所有任务。但是,收集方法负责任何线程问题,而不是隐藏在被调用的方法中)