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