从ASP.net项目

时间:2015-12-03 15:43:32

标签: c# asp.net

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

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

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

  3. 最后,静态方法可以完成一些工作(需要userId)

  4. 我相信这种方法是线程安全的,并且如果两个用户同时调用此方法,那么一切都将正确完成(让我们说同样的纳秒)。我是正确还是完全错误?如果我错了,在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调用静态方法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);
      }
    

    这是静态方法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));
            }
      }
    

3 个答案:

答案 0 :(得分:2)

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

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

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

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

答案 1 :(得分:2)

&#34;线程安全&#34;不是一个独立的术语。线程安全在面对什么?你期待什么样的并发修改?

让我们来看几个方面:

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

但是,您的代码似乎过于复杂。如果您希望能够长期保持这一点,我建议您大大简化它。

  • 不要使用ref参数,除非您真的知道自己在做什么(这里没有必要)。
  • 请勿将字符串与其他数据类型混淆,因此请尽可能避免使用ToString()绝对避免像Contains那样令人讨厌的技巧来检查密钥相等性。你希望你的代码在发生意外事件时中断,因为代码会一直li&#34;实际上几乎不可能调试(你写错误)。
  • 如果您真正做的唯一事情就是等待所有任务,那么就不要有效地返回一系列任务 - 不妨在被调用者那样做以简化API。
  • 不要使用redis。这可能只是一个分心 - 你已经有了另一个数据库,所以你不太可能在这里需要它,除了性能原因,并且添加整个额外的数据库引擎还为时过早对于假设的性能问题。有一个合理的可能性是,与只有一个数据库相比,需要额外连接的额外开销可能会使您的代码更慢,尤其是如果您无法保存许多SQL查询。

答案 2 :(得分:1)

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

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

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

虽然我认为在这种特殊情况下,一旦你重构它并没有看到任何问题,并且可以多次并行调用DeleteItemFromRedisAsync,那么你将遇到问题。原因是如果多个线程可以修改你的deleteTasks列表,那么你不再保证你全部收集它们(https://msdn.microsoft.com/en-us/library/dd997373(v=vs.110).aspx如果2个线程做了一个“添加”/添加到非线程安全的方式同时然后其中一个丢失了)所以你可能在等待所有这些任务完成时错过了一个任务。

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