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

Пожалуйста, помогите уточнить!!!

3 ответа

Решение

Из комментариев:

Я предполагал, что когда ConfigureAwait(false) используется (где угодно в стеке вызовов), выполнение с этой точки не приведет к взаимоблокировке.

Я не верю в черную магию, и вы не должны. Всегда старайтесь понять, что происходит, когда вы используете что-то в своем коде.

Когда ты await асинхронный метод, который возвращает Task или Task<T>есть неявный захват SynchronizationContext посредством TaskAwaitable генерируется Task.GetAwaiter метод.

Как только этот контекст синхронизации будет создан и вызов асинхронного метода завершится, TaskAwaitable пытается маршализовать продолжение (которое в основном является остальной частью вызовов метода после первого await ключевое слово) на SynchronizationContext (с помощью SynchronizationContext.Post) который был ранее захвачен. Если вызывающий поток заблокирован, ожидая завершения того же метода, вы попадаете в тупик.

Вы должны спросить себя, должен ли я предоставлять синхронные оболочки для асинхронных методов? 99 процентов времени ответ - нет. Вы должны использовать синхронный API, такой как один WebClient предложения.

Блокируется при использовании в 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
}

У меня такая же проблема. "ConfigureAwait(false)" не всегда может избежать блокировки.

public class HomeController : Controller
{
    public async Task<ActionResult> Index()
    {
        // This works !
        ViewBag.Title = GetAsync().Result;

        // This cause deadlock even with "ConfigureAwait(false)" !
        ViewBag.Title = PingAsync().Result;

        return View();
    }

    public async Task<string> GetAsync()
    {
        var uri = new Uri("http://www.google.com");
        return await new HttpClient().GetStringAsync(uri).ConfigureAwait(false);
    }

    public async Task<string> PingAsync()
    {
        var pingResult = await new Ping().SendPingAsync("www.google.com", 3).ConfigureAwait(false);

        return pingResult.RoundtripTime.ToString();
    }
}

Для приведенного выше кода "GetAsync()" работает, а "PingAsync()" - нет.

Но я обнаружил, что если я оберну асинхронный вызов в новую задачу и подожду этой задачи, PingAsync() сработает без "ConfigureAwait(false)":

var task = Task.Run(() => PingAsync());
task.Wait();
ViewBag.Title = task.Result;

Я не знаю причину, может кто-то может сказать мне разницу.

Другие вопросы по тегам