簡體   English   中英

異步等待行為同步

[英]Async-await behaving synchronously

我已經實現了一小段異步代碼,但我遇到了一個奇怪的行為。

基本上我想要做的是為一組多個“客戶端”運行一個初始化過程,我不想在隊列中處理它們(有些可能需要時間來處理,其他可能不需要)。 我只是希望它們全部完成,以便 go 進行下一步。 為了避免同時運行太多進程,我使用了信號量(臨時設置為 2)。

我遇到的問題是執行似乎是同步完成的。 這是一段代碼(縮小:無日志記錄,try/catch 等):

public void IntializeReportStructure(DateTimeOffset inReferenceDate)
{
    List<Task> theTaskCollection = new List<Task>();
    foreach (long theClientId in this.GetClientIdCollection())
    {
        Task theTask = this.InitializeClientAsync(theClientId, inReferenceDate.Year, inReferenceDate.Month);
        theTaskCollection.Add(theTask);
    }

    Task.WaitAll(theTaskCollection.ToArray());
}

private async Task InitializeClientAsync(long inClientId, int inReferenceYear, int inReferenceMonth)
{
    await this.Semaphore.WaitAsync();
    await this.InitializeClientReportAsync(inClientId);
    await this.InitializeClientSummaryAsync(inClientId, inReferenceYear, inReferenceMonth);
    this.Semaphore.Release();
}

以下是日志內容:

Waiting for semaphore for client 41. Current count is 2.
Entered semaphore for client 41. Current count is 1.
Semaphore has been released for client 41. Current count is 2.
Waiting for semaphore for client 12. Current count is 2.
Entered semaphore for client 12. Current count is 1.
Semaphore has been released for client 12. Current count is 2.
Waiting for semaphore for client 2. Current count is 2.
Entered semaphore for client 2. Current count is 1.
Semaphore has been released for client 2. Current count is 2.
Waiting for semaphore for client 261. Current count is 2.
Entered semaphore for client 261. Current count is 1.
Semaphore has been released for client 261. Current count is 2.
Waiting for semaphore for client 1. Current count is 2.
Entered semaphore for client 1. Current count is 1.
Semaphore has been released for client 1. Current count is 2.
Waiting for semaphore for client 6. Current count is 2.
Entered semaphore for client 6. Current count is 1.
Semaphore has been released for client 6. Current count is 2.

如您所見,每個任務只有在前一個任務完成時才會進入信號量步驟。 我之前說過進程是異步運行的:在循環內(將任務添加到列表時),任務狀態為“RanToCompletion”,IsCompleted 的值為“true”。 我沒有在日志中包含時間戳,但我們有一些“客戶端”需要 15 到 20 秒才能運行,同時該過程正在“等待”。

最后一個細節,“InitializeClientReportAsync”和“InitializeClientSummaryAsync”這兩個方法只是異步獲取數據和異步保存數據。 那里沒有什么奇怪的。

有趣的是我可以通過添加“await Task.Delay(1);”來獲得異步結果。 在信號量 WaitAsync 之后。

private async Task InitializeClientAsync(long inClientId, int inReferenceYear, int inReferenceMonth)
{
    await this.Semaphore.WaitAsync();
    await Task.Delay(1);
    await this.InitializeClientReportAsync(inClientId);
    await this.InitializeClientSummaryAsync(inClientId, inReferenceYear, inReferenceMonth);
    this.Semaphore.Release();
}

這是日志內容:

Waiting for semaphore for client 41. Current count is 2.
Waiting for semaphore for client 12. Current count is 1.
Waiting for semaphore for client 2. Current count is 0.
Waiting for semaphore for client 261. Current count is 0.
Waiting for semaphore for client 1. Current count is 0.
Waiting for semaphore for client 6. Current count is 0.
Entered semaphore for client 12. Current count is 0.
Entered semaphore for client 41. Current count is 0.
Semaphore has been released for client 12. Current count is 0.
Entered semaphore for client 2. Current count is 0.
Semaphore has been released for client 41. Current count is 0.
Entered semaphore for client 261. Current count is 0.
Semaphore has been released for client 261. Current count is 0.
Entered semaphore for client 1. Current count is 0.
Semaphore has been released for client 1. Current count is 0.
Entered semaphore for client 6. Current count is 0.
Semaphore has been released for client 2. Current count is 1.
Semaphore has been released for client 6. Current count is 2.

如您所見,進程在進入之前都首先進入信號量。此外,我們意識到客戶端“2”是初始化時間最長的。 這就是我最初對代碼的期望。

我不知道為什么一個簡單的Task.Delay(1)會改變我的進程的行為。 這沒有多大意義。 也許我錯過了一些明顯的東西,因為我太專注了。

編輯

正如評論中提到的,問題來自“異步”實體框架代碼。 我認為這是理所當然的。

為了示例,我簡化了底層方法的內容。 “IDbContext”只是我們在Entity Framework的DbContext class上使用的一個接口。 在我們的例子中,異步方法直接來自初始 class。

private async Task InitializeClientAsync(long inClientId, int inReferenceYear, int inReferenceMonth)
{
    await this.Semaphore.WaitAsync();
    await this.SomeTest();
    this.Semaphore.Release();
}

private async Task SomeTest()
{
    using (IDbContext theContext = this.DataAccessProvider.CreateDbContext())
    {
        List<X> theQueryResult = await theContext.Set<X>().ToListAsync<X>();
        foreach (X theEntity in theQueryResult)
        {
            theEntity.SomeProperty = "someValue";
        }

        await theContext.SaveChangesAsync();
    }
}

所以在這里,即使我檢索數百 MB 的實體並更新它們(全部使用 await 和 async 方法),一切都保持完全同步。 只有當這個任務完成時,下一個任務才會進入信號量。

我在 SaveChangesAsync() 之前添加了一個 Task.Delay(1),下一個 Task 在第一個任務完成之前進入信號量。 它確認這里的一切都是同步的(Task.Delay 除外)。 但我不能說為什么雖然...

感謝您的所有回答,我想我們能夠更深入地了解問題所在:不是真正的異步等待問題,而是更多實體框架查詢阻塞了進程。

我仍然不完全理解為什么他們不運行異步。 我的猜測是他們應該......我肯定會對此進行更多調查。

由於主題現在有點不同,我想我可以將此問題標記為已回答。

編輯

在結束問題之前,我想提供有關剩余問題的更多詳細信息。

我用 EF6 異步查詢 (ToListAsync) 做了一個小例子。 在這里,我希望在我們的日志中首先看到“STARTED”,然后是“PENDING”,然后在檢索數據后看到“FINISHED”。

private static async Task DoSomething()
{
    ILog theLogger = LogManager.GetLogger("test");
    using (Context theContext = new Context())
    {
        theLogger.Info("STARTING");
        Task<List<Transaction>> theTask = theContext.Set<Transaction>().ToListAsync();
        theLogger.Info("PENDING");
        var theResult = await theTask;
    }

    theLogger.Info("FINISHED");
}

2020-04-03 13:35:55,948 [1] INFO  test [(null)] - STARTING
2020-04-03 13:36:11,156 [1] INFO  test [(null)] - PENDING
2020-04-03 13:36:11,158 [1] INFO  test [(null)] - FINISHED

如您所見,“PENDING”發生在檢索數據之后(完成所有工作后)。 因此,無論您是否使用異步,結果都是一樣的。

我用一個簡單的 Task.Delay 而不是查詢嘗試了相同的示例。

private static async Task DoSomething()
{
    ILog theLogger = LogManager.GetLogger("test");
    using (Context theContext = new Context())
    {
        theLogger.Info("STARTING");
        Task theTask = Task.Delay(20000);
        theLogger.Info("PENDING");
        await theTask;
    }

    theLogger.Info("FINISHED");
}

2020-04-03 13:34:51,858 [1] INFO  test [(null)] - STARTING
2020-04-03 13:34:51,907 [1] INFO  test [(null)] - PENDING
2020-04-03 13:35:21,922 [5] INFO  test [(null)] - FINISHED

在這里,一切都很正常。 僅當遇到 await 關鍵字時,該過程才會暫停。

有沒有人遇到過這種行為? 正常嗎?

暫無
暫無

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

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