У меня есть многоуровневое приложение .Net 4.5, вызывающее метод с использованием ключевых слов C # new async
и, await
которые просто зависают, и я не понимаю, почему.
Внизу у меня есть асинхронный метод, который расширяет нашу утилиту базы данных OurDBConn
(в основном оболочка для базовых объектов DBConnection
и DBCommand
объектов):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Затем у меня есть асинхронный метод среднего уровня, который вызывает это, чтобы получить некоторые медленные итоги:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Наконец, у меня есть метод пользовательского интерфейса (действие MVC), который выполняется синхронно:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
Проблема в том, что он навсегда висит на последней строке. То же самое происходит, если я звоню asyncTask.Wait()
. Если я запустил медленный метод SQL напрямую, это займет около 4 секунд.
Я ожидаю, что когда это произойдет asyncTask.Result
, если он еще не закончен, он должен подождать, пока он не закончится, и, как только это произойдет, он должен вернуть результат.
Если я перейду через отладчик, оператор SQL завершится и лямбда-функция завершится, но return result;
строка GetTotalAsync
никогда не будет достигнута.
Есть идеи, что я делаю не так?
Есть предложения, где мне нужно изучить, чтобы это исправить?
Может быть, это где-то тупик, и если да, то есть ли прямой способ его найти?
SynchronizationContext
.async
/await
.Это классический
async
сценарий смешанного тупика, который я описываю в своем блоге . Джейсон хорошо это описал: по умолчанию «контекст» сохраняется при каждомawait
и используется для продолженияasync
метода. Этот «контекст» является текущим,SynchronizationContext
если онnull
не является текущимTaskScheduler
. Когдаasync
метод пытается продолжить, он сначала повторно вводит захваченный «контекст» (в данном случае ASP.NETSynchronizationContext
). ASP.NETSynchronizationContext
разрешает только один поток в контексте за раз, и в этом контексте уже есть поток - поток заблокированTask.Result
.Есть два совета, которые помогут избежать этого тупика:
async
до конца. Вы упомянули, что «не можете» этого сделать, но я не уверен, почему. ASP.NET MVC на .NET 4.5, безусловно, может поддерживатьasync
действия, и внести это несложно.ConfigureAwait(continueOnCapturedContext: false)
как можно больше. Это переопределяет поведение по умолчанию возобновления в захваченном контексте.источник
ConfigureAwait(false)
гарантия того, что текущая функция продолжается на другом контексте?async
действие, не нарушив того, как это работает на стороне клиента. Я определенно планирую изучить этот вариант в более долгосрочной перспективе.ConfigureAwait(false)
, решило бы использование дерева вызовов проблему OP.async
вообще не влияет на клиентскую сторону. Я объясняю это в другом сообщении блога,async
не меняет протокол HTTP .async
"расти" через кодовую базу. Если ваш метод контроллера может зависеть от асинхронных операций, тогда должен возвращаться метод базового классаTask<ActionResult>
.async
Всегда неудобно переходить к большому проекту, потому что смешиваниеasync
и синхронизация кода сложны и запутаны. Чистыйasync
код намного проще.Я был в той же ситуации взаимоблокировки, но в моем случае, вызывая метод async из метода синхронизации, для меня работает следующее:
это хороший подход, любая идея?
источник
Просто чтобы добавить к принятому ответу (недостаточно репутации для комментариев), у меня возникла эта проблема при блокировке using
task.Result
, событие, хотя каждое из них былоawait
нижеConfigureAwait(false)
, как в этом примере:На самом деле проблема заключалась в коде внешней библиотеки. Метод библиотеки async пытался продолжить работу в контексте синхронизации вызова, независимо от того, как я настроил ожидание, что привело к тупиковой ситуации.
Таким образом, ответ заключался в том, чтобы развернуть мою собственную версию кода внешней библиотеки
ExternalLibraryStringAsync
, чтобы она имела желаемые свойства продолжения.неправильный ответ для исторических целей
После долгой боли и мучений я нашел решение, похороненное в этом сообщении в блоге (Ctrl-f для «тупика»). Он вращается вокруг использования
task.ContinueWith
, а не гологоtask.Result
.Предыдущий пример взаимоблокировки:
Избегайте такой тупиковой ситуации:
источник
Task
он был завершен, и не предоставляете вызывающей стороне никаких средств определения, когда действительно происходит мутация возвращенного объекта.GetFooSynchronous
метод такой блок ?Task
вместо блокировки.быстрый ответ: измените эту строку
к
Зачем? вы не должны использовать .result, чтобы получить результат задач внутри большинства приложений, кроме консольных приложений, если вы это сделаете, ваша программа будет зависать, когда доберется туда
вы также можете попробовать приведенный ниже код, если хотите использовать .Result
источник