异步等待行为

时间:2020-04-03 05:55:01

标签: c# asynchronous async-await console-application semaphore

我已经实现了一小段异步代码,并且遇到了奇怪的行为。

基本上,我想为一组多个“客户端”运行一个初始化过程,而我不想在队列中处理它们(有些可能要花一些时间,而其他可能不会)。我只希望它们全部完成,然后再进行下一步。为了避免有太多的进程同时运行,我使用了Sempaphore(临时设置为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.

如您所见,每个任务仅在上一个任务完成时才进入信号量步骤。我之前说过,该过程异步运行:在循环内(将Task添加到列表中时),任务状态为“ 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.

如您所见,进程首先进入信号灯,然后才进入。Aslo,我们意识到客户端“ 2”是最长的初始化对象。这是我最初对代码的期望。

我不知道为什么简单的Task.Delay(1)会改变我过程的行为。这没有多大意义。也许我错过了一些明显的东西,因为我太专注了。

修改

如评论中所述,问题来自“异步”实体框架代码。我认为这是理所当然的。

为示例起见,我简化了底层方法的内容。 “ IDbContext”只是我们在实体框架的DbContext类上使用的接口。在我们的例子中,异步方法直接来自初始类。

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除外)。但是我不能说为什么...

1 个答案:

答案 0 :(得分:0)

感谢您的所有回答,我想我们可以对问题出在什么地方有更深入的了解:并不是真正的异步等待问题,而是更多的Entity Framework查询阻止了该过程。

我仍然不完全理解为什么他们不异步运行。我的猜测是他们应该……我一定会对此进行更多调查。

由于现在的话题有所不同,我想我可以将此问题标记为已回答。

修改

在结束问题之前,我想详细介绍其余问题。

我用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

如您所见,“待处理”发生在检索数据之后(当所有工作完成时)。因此,无论是否使用异步,结果都将相同。

我用一个简单的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关键字时,该过程才会暂停。

有人以前遇到过这种行为吗?正常吗?