Тупик даже после использования ConfigureAwait (false) в потоке Asp.Net
Я нахожусь в тупике даже после использования ConfigureAwait(false)
, ниже приведен пример кода.
В соответствии с образцом http://blog.stephencleary.com/2012/02/async-and-await.html (#Avoding Context), это не должно было быть заблокировано.
Это мой класс:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
Этот класс из общей библиотеки:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit
...
}
Работает, если я добавляю ConfigureAwait (false) для ожидания вызова в общей библиотеке, где выполняется вызов HttpClient:
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread.
...
}
Я просматриваю все найденные блоги, только различие, которое я нахожу, - ConfigureAwait (false) работает при использовании с httpClient.AsyncApi() call!?
Пожалуйста, помогите уточнить!!!
Ответы
Ответ 1
Из комментариев:
Я был в предположении, что когда ConfigureAwait (false) используется (любое место в стеке вызовов), выполнение с этой точки не приведет к тупиковой ошибке.
Я не верю в черную магию, и вы тоже. Всегда старайтесь понять, что происходит, когда вы используете что-то в своем коде.
Когда вы await
асинхронный метод, который возвращает Task
или Task<T>
, существует неявный захват SynchronizationContext
TaskAwaitable
, сгенерированный методом Task.GetAwaiter
.
Как только этот контекст синхронизации находится на месте и вызов метода асинхронного завершения завершен, TaskAwaitable
пытается упорядочить продолжение (которое в основном является остальным вызовом метода после первого ключевого слова await
) на SynchronizationContext
( используя SynchronizationContext.Post
), который был ранее захвачен. Если вызывающий поток заблокирован, ожидая завершения этого же метода, у вас есть тупик.
Вы должны спросить себя Должен ли я выставлять синхронные обертки для асинхронных методов? В 99% случаев ответ нет. Вы должны использовать синхронный API, например, один WebClient
.
Ответ 2
Он блокируется при использовании в ProjectsRetriever
, потому что:
public class ProjectsRetriever
{
public string GetProjects()
{
//querying the result blocks the thread and wait for result.
var projects = this.GetProjects(uri).Result;
... //require Thread1 to continue.
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//any thread can continue the method to return result because we use ConfigureAwait(false)
return await this.projectSystem.GetProjects(uri, Constants.UserName).ConfigureAwait(false);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects();
// code here is never hit because it requires Thread1 to continue its execution
// but Thread1 is blocked in var projects = this.GetProjects(uri).Result;
...
}
Он не блокируется при использовании в ProjectSystem
, потому что:
public class ProjectsRetriever
{
public string GetProjects()
{
...
var projects = this.GetProjects(uri).Result;
...//requires Thread1 to continue
...
}
private async Task<IEnumerable<Project>> GetProjects(Uri uri)
{
//requires Thread1 to continue
return await this.projectSystem.GetProjects(uri, Constants.UserName);
}
}
public class ProjectSystem
{
public async Task<IEnumerable<Project>> GetProjects(Uri uri, string userName)
{
var projectClient = this.GetHttpClient<ProjectHttpClient>(uri);
var projects = await projectClient.GetProjects().ConfigureAwait(false);
// no deadlock, resumes in a new thread. After this function returns, Thread1 could continue to run
}