Sincronize por Assíncrono evitando o impasse e evite que a interface seja responsiva

Temos uma biblioteca que está sendo usada por clientes WPF e / ou Winforms.

Fornecemos um método asynchronous semelhante a:

Task GetIntAsync() 

Nós também (infelizmente) fornecemos um método wrapper síncrono:

 int GetInt(); 

que basicamente apenas chama o método asynchronous e chama .Result sua tarefa.

Recentemente, percebemos que, em determinadas circunstâncias, algum código no GetIntAsync precisa ser executado no thread da interface principal (ele precisa usar um componente COM herdado que esteja marcado como modelo “Single” Threading (ou seja, o componente deve ser executado no thread STA principal não apenas) qualquer thread STA)

Então, o problema é que quando GetInt() é chamado no thread principal, ele será deadlock desde

  • o .Result bloqueia o thread principal,
  • o código dentro de GetIntAsync() usa Dispatcher.Invoke para tentar executar no thread principal.

O método síncrono já está sendo consumido, portanto, seria uma alteração para removê-lo. Por isso, optamos por usar o WaitWithPumping em nosso método GetInt() síncrono para permitir que a chamada para o thread principal funcione.

Isso funciona bem, exceto para clientes que usam GetInt() de seu código de interface do usuário. Anteriormente, eles esperavam que o uso de GetInt() deixasse sua UI sem resposta – isto é, se eles chamassem GetInt() no manipulador de events click de um botão, eles esperariam que nenhuma mensagem do Windows fosse processada até que o manipulador retornasse. Agora que as mensagens são bombeadas, sua interface do usuário é responsiva e esse mesmo botão pode ser clicado novamente (e eles provavelmente não codificaram seu manipulador para serem reentrantes).

Se houver uma solução razoável, gostaríamos que nossos clientes não precisassem codificar para que a interface respondesse durante uma chamada para o GetInt

Questão:

  • Existe uma maneira de fazer um WaitWithPumping que irá bombear mensagens “Invoke to main”, mas não bombear outras mensagens relacionadas à interface do usuário?
  • Seria suficiente para nossos propósitos se a interface do usuário do cliente se comportasse como se uma checkbox de modal dialog fosse mostrada atualmente, embora oculta (ou seja, o usuário não pudesse acessar as outras janelas). Mas, pelo que li, você não pode esconder um modal dialog.
  • Outras soluções alternativas que você possa pensar seriam apreciadas.

Em vez de utilizar a bomba de mensagens existente, você pode criar sua própria bomba de mensagens dentro do contexto do GetInt . Aqui está uma input de blog discutindo como escrever um. Esta é a solução completa que o blog cria .

Usando isso, você pode escrever como:

 public int GetInt() { return AsyncPump.Run(() => GetIntAsync()); } 

Isso resultará em bloquear completamente o thread da interface do usuário conforme o esperado, garantindo, ao mesmo tempo, que todas as continuações chamadas de GetIntAsync não GetIntAsync impasse, pois elas serão empacotadas para um SynchronizationContext diferente. Observe também que esta bomba de mensagens ainda está sendo executada no encadeamento STA / UI principal.