簡體   English   中英

從ASP.net項目調用靜態異步方法

[英]Calling static async methods from ASP.net project

我想知道這個場景是否是線程安全的,是否存在我目前沒有看到的問題:

  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調用靜態方法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));
        }
  }

注意:這個答案是在OP修改他們的問題之前發布的,以添加他們的代碼,這表明這實際上是async / await是否是線程安全的問題。


靜態方法本身不是問題。 如果靜態方法是自包含的並且僅使用局部變量設法完成其工作,那么它是完全線程安全的。

如果靜態方法不是自包含的(委托給線程不安全的代碼),或者它以非線程安全的方式操作靜態狀態,即在lock()外部讀取和寫入靜態變量,則會出現問題條款。

例如, int.parse()int.tryParse()是靜態的,但完全是線程安全的。 想象一下如果他們不是線程安全的恐怖。

“線程安全”不是一個獨立的術語。 線程安全在面對什么? 你期待什么樣的並發修改?

我們來看幾個方面:

  • 你自己的可變共享狀態:你在這段代碼中沒有任何共享狀態; 所以它是自動線程安全的。
  • 間接共享狀態: DatabaseContext 這看起來像一個sql數據庫,那些往往是線程“安全”,但究竟是什么意思取決於所討論的數據庫。 例如,您正在刪除Trenings行,如果某個其他線程也刪除了同一行,您可能會遇到(安全)並發沖突異常。 並且根據隔離級別,即使對於“Trenings”的其他特定突變,您也可能會遇到並發沖突異常。 在最壞的情況下,這意味着一個失敗的請求,但數據庫本身不會損壞。
  • Redis本質上是單線程的,因此所有操作都是序列化的,從這個意義上講,“線程安全”(這可能不會給你帶來太多幫助)。 您的刪除代碼獲取一組密鑰,然后最多刪除其中一個密鑰。 如果兩個或多個線程同時嘗試刪除相同的密鑰,則一個線程可能會嘗試刪除不存在的密鑰,這對您來說可能是意外的(但不會導致數據庫損壞)。
  • redis + sql之間的隱式一致性:看起來你正在使用guid,所以不相關的事情發生沖突的可能性很小。 您的示例僅包含刪除操作(可能不會導致一致性問題),因此很難推測在所有其他情況下redis和sql數據庫是否保持一致。 一般來說,如果您的ID永遠不會被重用,那么您可能是安全的 - 但保持兩個數據庫同步是一個難題,而且您很可能在某個地方犯錯誤。

但是,您的代碼似乎過於復雜。 如果您希望能夠長期保持這一點,我建議您大大簡化它。

  • 除非你真的知道自己在做什么,否則不要使用ref參數(這里沒有必要)。
  • 不要將字符串與其他數據類型混淆,因此請盡可能避免使用ToString() 絕對避免像Contains這樣令人討厭的技巧來檢查密鑰相等。 希望代碼在發生意外情況時中斷,因為“跛行”的代碼幾乎無法調試(並且您編寫錯誤)。
  • 如果您真正能做的唯一事情就是等待所有任務,那么不要有效地返回一系列任務 - 不妨在被調用者中這樣做以簡化API。
  • 不要使用redis。 這可能只是一個分心 - 你已經有了另一個數據庫,所以你不太需要它,除了性能原因,並且為假設的性能問題添加額外的數據庫引擎還為時過早。 有一個合理的可能性,即需要額外連接的額外開銷可能會使您的代碼比只有一個數據庫的速度慢 ,特別是如果您無法保存許多SQL查詢。

你在這里做的是在列表上同步(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個線程同時以非線程安全的方式執行“添加”/“添加到結束”,則其中1個丟失)因此您可能在等待所有任務完成時錯過了任務。

我也會避免混合范式。 使用async / await或跟蹤任務集合並讓方法添加到該列表。 不要兩者都做。 從長遠來看,這將有助於代碼的可維護性。 (注意,線程仍然可以返回一個任務,你收集它們然后等待它們全部。但是收集方法負責任何線程問題,而不是隱藏在被調用的方法中)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM