Asyn c -aait ведет себя синхронно - PullRequest
0 голосов
/ 03 апреля 2020

Я реализовал небольшой фрагмент асинхронного кода и столкнулся со странным поведением.

По сути, я хочу запустить процесс инициализации для набора из нескольких «клиентов», и я не хочу обрабатывать их в очереди (некоторым может потребоваться время для обработки, другим - нет). Я просто хочу, чтобы они все закончили, чтобы go перейти к следующему шагу. Чтобы избежать одновременного запуска слишком большого числа процессов, я использую семафор (временно установлен на 2).

Проблема, с которой я столкнулся, заключается в том, что выполнение выполняется синхронно. Вот фрагмент кода (минимизированный: без регистрации, попытка / отлов и т. Д. c):

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.

Как видите, каждая задача идет к шагу семафора только после завершения предыдущей. Ранее я говорил, что процесс выполняется асинхронно: в l oop (при добавлении задачи в список) состояние задачи равно «RanToCompletion», а значение IsCompleted равно «true». Я не включил метку времени в журналы, но у нас есть какой-то «клиент», который запускается через 15–20 секунд, а процесс тем временем «ждет». * "и" InitializeClientSummaryAsyn c "просто делают asyn c получают данные и asyn c сохраняют данные. Ничего странного там нет.

забавная часть в том, что я могу получить асинхронный результат, добавив "await Task.Delay (1);" сразу после семафора WaitAsyn c.

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) меняет поведение моего процесса. Это не имеет особого смысла. Может быть, я упускаю что-то очевидное, потому что я слишком сосредоточен.

Редактировать

Как упоминалось в комментарии, проблема возникает из "asyn c" Entity Framework код. Я считал это само собой разумеющимся.

Я упростил содержание базовых методов для примера. «IDbContext» - это просто интерфейс, который мы используем в классе DbContext Entity Framework. В нашем случае методы asyn c напрямую относятся к исходному классу.

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

Так что здесь, даже если я получу сотни МБ сущностей и обновлю их (все с использованием await и asyn * 1050) * методы), все остается полностью синхронным. Следующая задача войдет в семафор только после его завершения.

Я добавил Task.Delay (1) перед SaveChangesAsyn c (), и следующая задача входит в семафор до завершения первой задачи. Это подтверждает, что все здесь синхронно (кроме Task.Delay). Но я не могу сказать, почему, хотя ...

1 Ответ

0 голосов
/ 03 апреля 2020

Благодаря всем вашим ответам, я думаю, мы смогли ближе рассмотреть проблему: на самом деле проблема не в асин c -wait, а в запросах Entity Framework, блокирующих процесс.

Я до сих пор не до конца понимаю, почему они не работают asyn c. Я думаю, что они должны ... Я определенно расскажу больше об этом.

Поскольку топи c теперь немного отличается, я думаю, я могу пометить этот вопрос как ответ.

Редактировать

Прежде чем закрыть вопрос, я бы хотел подробнее рассказать об оставшейся проблеме.

Я сделал небольшой пример с асинхронным запросом EF6 c (ToListAsyn c). Здесь я ожидал увидеть в наших журналах сначала «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» происходит после получения данных (когда вся работа выполнена). Таким образом, результат будет одинаковым независимо от того, используете вы asyn c или нет.

Я попробовал этот же пример с простым 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.

Кто-нибудь сталкивался с таким поведением раньше? Это нормально?

...