ti-enxame.com

Um exemplo assíncrono/aguardar que causa um impasse

Eu me deparei com algumas práticas recomendadas para programação assíncrona usando as palavras-chave async/await do c # (eu sou novo no c # 5.0).

Um dos conselhos dados foi o seguinte:

Estabilidade: conheça seus contextos de sincronização

... Alguns contextos de sincronização são não-reentrantes e single-threaded. Isso significa que apenas uma unidade de trabalho pode ser executada no contexto em um determinado momento. Um exemplo disso é o thread da interface do usuário do Windows ou o contexto de solicitação do ASP.NET. Nestes contextos de sincronização de thread único, é fácil bloquear a si mesmo. Se você gerar uma tarefa a partir de um contexto de encadeamento único e, em seguida, aguardar essa tarefa no contexto, seu código de espera poderá estar bloqueando a tarefa em segundo plano.

public ActionResult ActionAsync()
{
    // DEADLOCK: this blocks on the async task
    var data = GetDataAsync().Result;

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}

Se eu tentar dissecá-lo sozinho, o thread principal gera um novo em MyWebService.GetDataAsync();, mas como o thread principal aguarda lá, ele aguarda o resultado em GetDataAsync().Result. Enquanto isso, digamos que os dados estejam prontos. Por que o thread principal não continua sua lógica de continuação e retorna um resultado de string de GetDataAsync()?

Alguém por favor pode me explicar por que há um impasse no exemplo acima? Eu sou completamente ignorante sobre qual é o problema ...

79
Dror Weiss

Veja um exemplo em aqui , Stephen tem uma resposta clara para você:

Então é isso que acontece, começando com o método de nível superior (Button1_Click for UI/MyController.Get para ASP.NET):

  1. O método de nível superior chama GetJsonAsync (dentro do contexto UI/ASP.NET).

  2. GetJsonAsync inicia a solicitação REST chamando HttpClient.GetStringAsync (ainda dentro do contexto).

  3. GetStringAsync retorna uma tarefa incompleta, indicando que a solicitação REST não está completa.

  4. GetJsonAsync aguarda a tarefa retornada por GetStringAsync. O contexto é capturado e será usado para continuar executando o método GetJsonAsync Mais tarde. GetJsonAsync retorna uma Tarefa não concluída, Indicando que o método GetJsonAsync não está completo.

  5. O método de nível superior bloqueia de maneira síncrona na tarefa retornada por GetJsonAsync. Isso bloqueia o thread de contexto.

  6. ... Eventualmente, a solicitação REST será concluída. Isso conclui a tarefa retornada pelo GetStringAsync.

  7. A continuação do GetJsonAsync está agora pronta para ser executada e aguarda que o contexto esteja disponível para que possa ser executado no contexto.

  8. Impasse. O método de nível superior está bloqueando o encadeamento de contexto, aguardando a conclusão do GetJsonAsync e o GetJsonAsync está aguardando que O contexto fique livre para que possa ser concluído. Para o exemplo da interface do usuário, o "Contexto" é o contexto da interface do usuário; para o exemplo do ASP.NET, o "contexto" é o contexto de solicitação do ASP.NET. Esse tipo de impasse pode ser causado por Ou "contexto".

Outro link que você deve ler:

Aguardar e interface do usuário e deadlocks! Oh meu!

68
cuongle
  • Fato 1: GetDataAsync().Result; será executado quando a tarefa retornada por GetDataAsync() for concluída, enquanto bloqueia o thread da interface do usuário
  • Fato 2: A continuação do await (return result.ToString()) é colocada em fila no thread da interface do usuário para execução
  • Fato 3: A tarefa retornada por GetDataAsync() será concluída quando sua continuação enfileirada for executada
  • Fato 4: A continuação enfileirada nunca é executada, porque o thread da interface do usuário está bloqueado (Fato 1)

Impasse!

O impasse pode ser quebrado por alternativas fornecidas para evitar o Fato 1 ou Fato 2.

  • Evite 1,4. Em vez de bloquear o thread da interface do usuário, use var data = await GetDataAsync(), que permite que o thread da interface do usuário continue em execução
  • Evite 2,3. Fila a continuação do aguardo para um thread diferente que não está bloqueado, por ex. use var data = Task.Run(GetDataAsync).Result, que postará a continuação no contexto de sincronização de um thread de threadpool. Isso permite que a tarefa retornada por GetDataAsync() seja concluída. 

Isso é explicado muito bem em um artigo de Stephen Toub , cerca de metade do caminho onde ele usa o exemplo de DelayAsync().

11
Phillip Ngan

Eu estava apenas mexendo com esse problema novamente em um projeto MVC.Net. Quando você quiser chamar métodos assíncronos de um PartialView, você não poderá fazer o PartyncView assíncrono. Você receberá uma exceção se o fizer.

Portanto, basicamente, uma solução simples no cenário em que você deseja chamar um método assíncrono de um método de sincronização, você pode fazer o seguinte:

  1. antes da chamada, limpe o SynchronizationContext
  2. fazer a chamada, não haverá mais impasse aqui, espere que termine
  3. restaurar o SynchronizationContext

Exemplo:

    public ActionResult DisplayUserInfo(string userName)
    {
        // trick to prevent deadlocks of calling async method 
        // and waiting for on a sync UI thread.
        var syncContext = SynchronizationContext.Current;
        SynchronizationContext.SetSynchronizationContext(null);

        //  this is the async call, wait for the result (!)
        var model = _asyncService.GetUserInfo(Username).Result;

        // restore the context
        SynchronizationContext.SetSynchronizationContext(syncContext);

        return PartialView("_UserInfo", model);
    }
10
Herre Kuijpers

Outro ponto importante é que você não deve bloquear tarefas, e usar async completamente para evitar deadlocks. Então será tudo assíncrono não sincronizado de bloqueio.

public async Task<ActionResult> ActionAsync()
{

    var data = await GetDataAsync();

    return View(data);
}

private async Task<string> GetDataAsync()
{
    // a very simple async method
    var result = await MyWebService.GetDataAsync();
    return result.ToString();
}
2
marvelTracker

Um trabalho em torno de que cheguei é usar um método de extensão Join na tarefa antes de solicitar o resultado.

A aparência do código é assim:

public ActionResult ActionAsync()
{
  var task = GetDataAsync();
  task.Join();
  var data = task.Result;

  return View(data);
}

Onde o método de junção é:

public static class TaskExtensions
{
    public static void Join(this Task task)
    {
        var currentDispatcher = Dispatcher.CurrentDispatcher;
        while (!task.IsCompleted)
        {
            // Make the dispatcher allow this thread to work on other things
            currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
        }
    }
}

Eu não sou suficiente no domínio para ver as desvantagens desta solução (se houver)

0
Orace