Тупик даже после использования 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
}