0

При преобразовании существующего синхронного метода в async я случайно использовал «асинхронный void» для одного из методов, что привело к неожиданному поведению.DbContext, находящийся слишком рано в методе Async

Ниже приведен упрощенный пример такого изменения я фактически выполненным,

public IActionResult Index() 
{ 
    var vm = new ViewModel(); 
    try 
    { 
     var max = 0; 

     if (_dbContext.OnlyTable.Any()) 
     { 
      max = _dbContext.OnlyTable.Max(x => x.SomeColumn); 
     } 

     _dbContext.Add(new TestTable() { SomeColumn = max + 1 }); 
     _dbContext.SaveChanges(); 

     MakePostCallAsync("http:\\google.com", vm); 

     if (!string.IsNullOrEmpty(vm.TextToDisplay)) 
     { 
      vm.TextToDisplay = "I have inserted the value " + newmax + " into table (-1 means error)"; 
     } 
     else 
     { 
      vm.TextToDisplay = "Errored!"; 
     } 


    } 
    catch (Exception ex) 
    { 
     vm.TextToDisplay = "I encountered error message - \"" + ex.Message + "\""; 
    } 
    return View("Index", vm); 
} 

private async void MakePostCallAsync(string url, ViewModel vm) 
{ 
    var httpClient = new HttpClient(); 

    var httpResponse = await httpClient.PostAsync("http://google.com", null).ConfigureAwait(true); 

    newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn); 
} 

Вопрос заключается в том, что метод MakePostCallAsync(), при попытке запроса к базе данных с помощью DbContext, бросает исключение, говоря DbContext является уже размещены.

_dbContext в примере вводится с использованием расширения ASP .Net Core (через расширение AddDbContext()) с его областью по умолчанию (область действия).

я не и должен помочь понять следующее,

  • почему контекст DB расположен еще до запроса подается, в то время как срок служба контекстного объекта должна быть всей продолжительностью текущего запроса
  • Даже хотя я использовал ConfigureAwait (true) (что явно означает, что после того, как ожидаемый метод возвращает MakePostCallAsync(), должен продолжаться в контексте запроса?) - это, похоже, не происходит
  • В фактическом коде это было исправлено после того, как я сделал все методы async (вплоть до контроллера) - в репродукции у меня есть sh isd, даже это не помогает предотвратить исключение - почему это так, как я могу это решить?

Репро доступен в https://github.com/jjkcharles/SampleAsync

ответ

0

В вашем примере вы не ожидающего свой призыв к MakePostCallAsync("http:\\google.com", vm). Это означает, что запрос продолжает выполнение немедленно, и в конечном итоге он выполняет код, который имеет ваш _dbContext, предположительно, в то время как MakePostCallAsync все еще ожидает, пока HTTP-клиент вернет ответ. Как только HTTP-клиент вернет ответ, ваш MakePostCallAsync попытается позвонить newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn), но ваш запрос уже обработан, и ваш DB-контекст удаляется к тому времени.

+0

Но почему контекст запроса не сохраняется даже после того, как я попросил его сохранить (ConfigureAwait (True)). Кроме того, ожидание на MakePostCallAsync() не изменило способ поведения кода –

3

Вы не должны использовать async void, если вы не пишете обработчик событий. Если вы хотите использовать асинхра/ждать вам нужно пройти весь путь вверх по стеке вызовов, пока вы получите, чтобы вернуть Task<IActionResult>

public async Task<IActionResult> Index() 
{ 
    var vm = new ViewModel(); 
    try 
    { 
     var max = 0; 

     if (_dbContext.OnlyTable.Any()) 
     { 
      max = _dbContext.OnlyTable.Max(x => x.SomeColumn); 
     } 

     _dbContext.Add(new TestTable() { SomeColumn = max + 1 }); 
     _dbContext.SaveChanges(); 

     await MakePostCallAsync("http:\\google.com", vm); 

     if (!string.IsNullOrEmpty(vm.TextToDisplay)) 
     { 
      vm.TextToDisplay = "I have inserted the value " + newmax + " into table (-1 means error)"; 
     } 
     else 
     { 
      vm.TextToDisplay = "Errored!"; 
     } 


    } 
    catch (Exception ex) 
    { 
     vm.TextToDisplay = "I encountered error message - \"" + ex.Message + "\""; 
    } 
    return View("Index", vm); 
} 

private async Task MakePostCallAsync(string url, ViewModel vm) 
{ 
    var httpClient = new HttpClient(); 

    var httpResponse = await httpClient.PostAsync("http://google.com", null).ConfigureAwait(true); 

    newmax = _dbContext.OnlyTable.Max(x => x.SomeColumn); 
} 
+0

Спасибо, я понимаю, что использование async void неверно. См. Мой третий пункт, меняя все на async/wait, не исправлял проблему для меня. В репозитории GitHub все изменилось на асинхронный, но по-прежнему не удается с той же ошибкой –

0

Мне кажется, у вас есть некоторые проблемы с пониманием, как использовать асинхронную-ЖДИ, потому что Я вижу несколько серьезных ошибок в вашей программе.

This article, написанный столь полезным помощником Стивен Клири помог мне понять, как правильно его использовать.

Каждая функция, которая хочет использовать асинхронную-ОЖИДАНИЕ должен вернуться Task вместо void и Task<TResult> вместо TResult. Единственным исключением из этого правила являются обработчики событий, которые не заинтересованы в результате действий.

Таким образом, первое изменение Index такое, что оно возвращает Task<IActionResult>. Как только вы это сделаете, ваш компилятор, вероятно, предупредит вас, что вы забыли зайти в свою функцию индекса.

Если вы вызываете функцию async, вам не нужно ждать ее завершения, вы можете делать другие полезные вещи, которые будут выполняться всякий раз, когда функция async должна что-то ждать. Но прежде, чем вы можете использовать любой результат функции асинхронной вы должны ждать, пока он не будет готов:

var taskMakePostCall = MakePostCallAsync(...) 
// if you have something useful to do, don't await 
DoSomethingUseful(...); 
// now you need the result of MakePostCallAsync, await until it is finished 
await TaskMakePostCall; 
// now you can use the results. 

Если MakePostCallAsync вернулся бы что-то, например, INT, возвращаемое значение было бы Task<int> и код был бы:

Task<int> taskMakePostCall = MakePostCallAsync(...) 
DoSomethingUseful(...); 
int result = await TaskMakePostCall; 

Если вы не имеете что-то полезное делать, просто ждать сразу:

int result = await MakePostCallAsync(...); 

причина вашего исключение заключается в том, что ваш MakePostCallAsync не закончен полностью, прежде чем вы где-нибудь упустите свой dbContext, возможно, с помощью инструкции using. После добавления этого ожидания перед возвратом вы убедитесь, что MakePostCallAsync полностью завершил работу перед возвратом Index()