Почему этот призыв к .Результату не провоцирует тупик? - PullRequest
0 голосов
/ 02 июля 2018

Я все еще пытаюсь обернуть голову, когда могу использовать .Результат, а когда не могу. Я пытался полностью избежать этого, используя асинхронный режим "полностью вниз".

Выполняя некоторую работу в нашем ASP.NET веб-приложении (НЕ ядро), я наткнулся на этот код. Теперь код работает (и работает уже 2 года), поэтому я знаю, что он работает, я просто не знаю, почему он работает.

Это запускается из метода контроллера синхронизации. Это цепочка вызовов методов, которая проходит через несколько уровней, пока, наконец, не выполнит некоторую работу HTTP. Я упростил код здесь:

// Class1
public byte[] GetDocument1(string url)
{
    return class2.GetDocument2(url).Result;
}

// Class2
public Task<byte[]> GetDocument2(string url)
{
    return class3.GetDocument3(url)
}

// Class3
public async Task<byte[]> GetDocument3(string url)
{
    var client = GetHttpClient(url);
    var resp = client.GetAsync(url).Result;

    if(resp.StatusCode == HttpStatusCode.OK)
    {
        using(var httpStream = await resp.Content.ReadAsStreamAsync())
        {
            // we are also using
            await httpStream.ReadAsync(...);
        }
    }
}

Итак, насколько я могу судить, когда все это начинается, я нахожусь в "главном контексте синхронизации ASP" (я запускаю в контроллере и в конечном итоге получаю этот код). Мы не используем .ConfigureAwait(false), поэтому я считаю, что мы всегда возвращаемся к этому контексту.

  1. В GetDocument3, почему не client.GetAsync(url).Result тупик?
  2. GetDocument3 смешивает .Result с await материалом. Вообще это хорошая идея ?? Здесь все в порядке, потому что .Result приходит раньше, чем ждет?
  3. В GetDocument1, почему не .Result тупик?

1 Ответ

0 голосов
/ 02 июля 2018

client.GetAsync(url).Result синхронно блокируется. resp.Content.ReadAsStreamAsync() на самом деле ничего не ждет. Task уже завершен, потому что HttpCompletionOption.ResponseContentRead используется в GetAsync, поэтому весь блок кода здесь представляет собой синхронный код, претендующий на асинхронность.

В общем, вы никогда не должны использовать .Result или .Wait или Task.Wait* - если вам абсолютно необходимо, используйте GetAwaiter().GetResult(), который не генерирует исключения, заключенные в AggregateException s, для которых вы можете не иметь catch, но даже этого следует избегать, как чумы. Вы вправе использовать async до конца.

Взаимная блокировка является проблемой только в контексте, который хочет вернуться обратно в исходный поток (например, пользовательский интерфейс или ASP.NET), и этот поток блокируется синхронным ожиданием. Если вы всегда используете ConfigureAwait(false) на каждые await, , кроме случаев , вы знаете, что вам действительно нужно сохранить контекст в продолжении (обычно только в обработчике событий пользовательского интерфейса верхнего уровня, потому что вы необходимо что-то обновить в пользовательском интерфейсе или на верхнем уровне контроллера ASP.NET), тогда вы должны быть в безопасности.

Синхронная блокировка внутри асинхронного кода также не годится, но не приведет к тупику. Использование await с ConfigureAwait(true) (по умолчанию) внутри синхронного ожидания в контексте, который хочет вернуться назад к определенному потоку, приведет к взаимоблокировке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...