Aqui temos um Grid
com a Button
. Quando o usuário clica no botão, um método em uma classe Utility é executado, o que força o aplicativo a receber um clique em Grade. O fluxo de código deve parar aqui e não continuar até o usuário clicar no Grid
.
Eu tive uma pergunta semelhante antes aqui:
Aguarde até o usuário clicar em C # WPF
Nessa pergunta, recebi uma resposta usando async / waitit, que funciona, mas como vou usá-lo como parte de uma API, não quero usar async / waitit, pois os consumidores terão que marcar seus métodos com assíncrono que eu não quero.
Como escrevo Utility.PickPoint(Grid grid)
método para atingir esse objetivo?
Vi isso que pode ajudar, mas ainda não o entendi completamente, para ser sincero:
Bloqueando até que um evento seja concluído
Considere-o como algo como o método Console.ReadKey () em um aplicativo de console. Quando chamamos esse método, o fluxo de código para até inserirmos algum valor. O depurador não continua até inserirmos algo. Eu quero o comportamento exato para o método PickPoint (). O fluxo do código será interrompido até o usuário clicar na Grade.
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="3*"/>
<RowDefinition Height="1*"/>
</Grid.RowDefinitions>
<Grid x:Name="View" Background="Green"/>
<Button Grid.Row="1" Content="Pick" Click="ButtonBase_OnClick"/>
</Grid>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
// do not continue the code flow until the user has clicked on the grid.
// so when we debug, the code flow will literally stop here.
var point = Utility.PickPoint(View);
MessageBox.Show(point.ToString());
}
}
public static class Utility
{
public static Point PickPoint(Grid grid)
{
}
}
fonte
Aync/Await
como fazer a Operação A e salvar a operação STATE agora que você deseja que o usuário clique em Grade .. por isso, se o usuário clicar em Grade, você verificará o estado se verdadeiro e, em seguida, faça sua operação ou faça o que quiser?AutoResetEvent
não é o que você quer?var point = Utility.PickPoint(Grid grid);
no método Grid Click? faça alguma operação e retorne a resposta?Respostas:
Sua abordagem está errada. Orientado a eventos não significa bloquear e aguardar um evento. Você nunca espera, pelo menos tenta sempre evitá-lo. Esperar está desperdiçando recursos, bloqueando threads e talvez introduzindo o risco de um deadlock ou thread zumbi (caso o sinal nunca seja gerado).
Deve ficar claro que o bloqueio de um encadeamento para aguardar um evento é um antipadrão, pois contradiz a ideia de um evento.
Você geralmente tem duas opções (modernas): implementar uma API assíncrona ou uma API orientada a eventos. Como você não deseja implementar sua API assíncrona, você fica com a API orientada a eventos.
A chave de uma API orientada a eventos é que, em vez de forçar o chamador a aguardar de forma síncrona por um resultado ou a pesquisar por um resultado, você deixa o chamador continuar e envia uma notificação a ele assim que o resultado estiver pronto ou a operação for concluída. Enquanto isso, o chamador pode continuar executando outras operações.
Ao analisar o problema de uma perspectiva de encadeamento, a API orientada a eventos permite que o encadeamento de chamada, por exemplo, o encadeamento da interface do usuário, que executa o manipulador de eventos do botão, seja livre para continuar lidando com operações relacionadas à interface do usuário, como renderizar elementos da interface do usuário ou manipular entrada do usuário, como movimento do mouse e pressionamentos de teclas. O mesmo efeito como uma API assíncrona, embora menos conveniente.
Como você não forneceu detalhes suficientes sobre o que realmente está tentando fazer, o que
Utility.PickPoint()
realmente está fazendo e qual é o resultado da tarefa ou por que o usuário deve clicar no botão `Grade, não posso oferecer uma solução melhor . Apenas posso oferecer um padrão geral de como implementar sua exigência.Seu fluxo ou objetivo é obviamente dividido em pelo menos duas etapas para torná-lo uma sequência de operações:
Grid
com pelo menos duas restrições:
Isso requer duas notificações para o cliente da API para permitir a interação sem bloqueio:
Você deve permitir que sua API implemente esse comportamento e restrições expondo dois métodos públicos e dois eventos públicos.
Implementar / refatorar API do utilitário
Utility.cs
PickPointCompletedEventArgs.cs
Use a API
MainWindow.xaml.cs
MainWindow.xaml
Observações
Eventos gerados em um encadeamento em segundo plano executam seus manipuladores no mesmo encadeamento. O acesso a
DispatcherObject
um elemento de interface do usuário de um manipulador, que é executado em um encadeamento em segundo plano, requer que a operação crítica seja enfileirada para oDispatcher
uso de umDispatcher.Invoke
ouDispatcher.InvokeAsync
para evitar exceções de encadeamento.Algumas reflexões - responda aos seus comentários
Como você estava se aproximando de mim para encontrar uma solução "melhor" de bloqueio, me deu o exemplo de aplicativos de console, senti-me convencido de que sua percepção ou ponto de vista está totalmente errado.
Um aplicativo de console é algo totalmente diferente. O conceito de segmentação é diferente. Os aplicativos de console não têm uma GUI. Apenas fluxos de entrada / saída / erro. Você não pode comparar a arquitetura de um aplicativo de console com um aplicativo GUI avançado. Isso não vai funcionar. Você realmente deve entender e aceitar isso.
O WPF é criado em torno de um thread de renderização e de um thread da interface do usuário. Esses threads continuam sempre girando para se comunicar com o sistema operacional, como lidar com a entrada do usuário - mantendo o aplicativo responsivo . Você nunca deseja pausar / bloquear esse segmento, pois isso impedirá que a estrutura faça um trabalho essencial em segundo plano (como responder a eventos do mouse - você não deseja que o mouse congele):
espera = bloqueio de thread = falta de resposta = UX ruim = usuários / clientes irritados = problemas no escritório.
Às vezes, o fluxo do aplicativo exige que a entrada ou a rotina seja concluída. Mas não queremos bloquear o segmento principal.
É por isso que as pessoas inventaram modelos de programação assíncronos complexos, para permitir a espera sem bloquear o encadeamento principal e sem forçar o desenvolvedor a escrever código de multithreading complicado e incorreto.
Toda estrutura de aplicativo moderna oferece operações assíncronas ou um modelo de programação assíncrono, para permitir o desenvolvimento de código simples e eficiente.
O fato de você estar se esforçando para resistir ao modelo de programação assíncrona mostra alguma falta de entendimento para mim. Todo desenvolvedor moderno prefere uma API assíncrona a uma síncrona. Nenhum desenvolvedor sério se preocupa em usar a
await
palavra - chave ou em declarar seu métodoasync
. Ninguém. Você é o primeiro a encontrar pessoas que reclamam de APIs assíncronas e que as consideram inconvenientes de usar.Se eu verificar sua estrutura, que é um objetivo para resolver problemas relacionados à interface do usuário ou facilitar as tarefas relacionadas à interface do usuário, espero que seja assíncrona - o tempo todo.
A API relacionada à interface do usuário que não é assíncrona é um desperdício, pois complicará meu estilo de programação, portanto, meu código, que se torna mais suscetível a erros e difícil de manter.
Uma perspectiva diferente: quando você reconhece que a espera bloqueia o encadeamento da interface do usuário, criando uma experiência do usuário muito ruim e indesejável, pois a interface congela até a espera terminar, agora que você percebe isso, por que ofereceria uma API ou um modelo de plug-in que incentive um desenvolvedor para fazer exatamente isso - implementar espera?
Você não sabe o que o plug-in de terceiros fará e quanto tempo uma rotina levará até ser concluída. Este é simplesmente um design de API ruim. Quando sua API opera no encadeamento da interface do usuário, o chamador da sua API deve poder fazer chamadas sem bloqueio.
Se você negar a única solução barata ou graciosa, use uma abordagem orientada a eventos, como mostrado no meu exemplo.
Ele faz o que você deseja: iniciar uma rotina - aguardar a entrada do usuário - continuar a execução - atingir a meta.
Eu realmente tentei várias vezes para explicar por que esperar / bloquear é um design de aplicativo ruim. Novamente, você não pode comparar uma interface do usuário do console com uma interface gráfica rica, onde, por exemplo, o tratamento de entrada por si só é uma multidão mais complexa do que apenas ouvir o fluxo de entrada. Realmente não sei o seu nível de experiência e onde você começou, mas você deve começar a adotar o modelo de programação assíncrona. Não sei o motivo pelo qual você tenta evitá-lo. Mas não é nada sábio.
Hoje, os modelos de programação assíncrona são implementados em qualquer lugar, em qualquer plataforma, compilador, ambiente, navegador, servidor, desktop, banco de dados - em qualquer lugar. O modelo orientado a eventos permite atingir o mesmo objetivo, mas é menos conveniente de usar (inscrever-se / cancelar a inscrição de / para eventos), contando com encadeamentos em segundo plano. O controle de eventos é antiquado e deve ser usado apenas quando bibliotecas assíncronas não estão disponíveis ou não são aplicáveis.
O comportamento (o que você experimenta ou observa) é muito diferente de como essa experiência é implementada. Duas coisas diferentes É muito provável que a sua Autodesk esteja usando bibliotecas assíncronas ou recursos de linguagem ou algum outro mecanismo de encadeamento. E também é relacionado ao contexto. Quando o método que está em sua mente está executando em um encadeamento em segundo plano, o desenvolvedor pode optar por bloquear esse encadeamento. Ele tem uma boa razão para fazer isso ou apenas fez uma má escolha de design. Você está totalmente no caminho errado;) O bloqueio não é bom.
(O código-fonte da Autodesk é de código aberto? Ou como você sabe como é implementado?)
Eu não quero te ofender, por favor acredite em mim. Mas, reconsidere a implementação da sua API assíncrona. É apenas na sua cabeça que os desenvolvedores não gostam de usar async / waitit. Você obviamente entendeu errado. E esqueça o argumento do aplicativo de console - é um absurdo;)
API relacionada à interface do usuário DEVE usar async / wait sempre que possível. Caso contrário, você deixará todo o trabalho para escrever um código sem bloqueio no cliente da sua API. Você me forçaria a agrupar todas as chamadas à sua API em um thread em segundo plano. Ou para usar um tratamento de eventos menos confortável. Acredite em mim - todo desenvolvedor prefere decorar seus membros do
async
que manipular eventos. Toda vez que você usa eventos, você pode arriscar um possível vazamento de memória - depende de algumas circunstâncias, mas o risco é real e não é raro na programação descuidada.Eu realmente espero que você entenda por que o bloqueio é ruim. Eu realmente espero que você decida usar async / waitit para escrever uma API assíncrona moderna. No entanto, mostrei a você uma maneira muito comum de esperar sem bloqueio, usando eventos, embora exorto você a usar async / waitit.
Se você não deseja permitir que o plug-in tenha acesso direto aos elementos da interface do usuário, forneça uma interface para delegar eventos ou expor componentes internos por meio de objetos abstratos.
A API internamente se inscreverá em eventos da interface do usuário em nome do suplemento e, em seguida, delega o evento expondo um evento "wrapper" correspondente ao cliente da API. Sua API deve oferecer alguns ganchos nos quais o suplemento pode se conectar para acessar componentes de aplicativos específicos. Uma API de plug-in atua como um adaptador ou fachada para fornecer acesso externo a internos.
Para permitir um certo grau de isolamento.
Veja como o Visual Studio gerencia plug-ins ou nos permite implementá-los. Finja que deseja escrever um plug-in para o Visual Studio e faça alguma pesquisa sobre como fazer isso. Você perceberá que o Visual Studio expõe seus componentes internos por meio de uma interface ou API. Por exemplo, você pode manipular o editor de código ou obter informações sobre o conteúdo do editor sem acesso real a ele.
fonte
var str = Console.ReadLine(); Console.WriteLine(str);
O que acontece quando você executa o aplicativo no modo de depuração. Ele irá parar na primeira linha do código e forçará você a inserir um valor na interface do usuário do console e, depois de inserir algo e pressionar Enter, ele executará a próxima linha e realmente imprimirá o que você digitou. Eu estava pensando exatamente o mesmo comportamento, mas no aplicativo WPF.Pessoalmente, acho que isso está sendo muito complicado para todos, mas talvez eu não esteja entendendo completamente o motivo pelo qual isso precisa ser feito de uma certa maneira, mas parece que uma simples verificação de bool pode ser usada aqui.
Em primeiro lugar, torne sua grade testável, definindo as propriedades
Background
eIsHitTestVisible
, caso contrário ela nem capturará cliques do mouse.Em seguida, crie um valor bool que possa armazenar se o evento "GridClick" deve ocorrer. Quando a grade é clicada, verifique esse valor e a execução do evento de clique da grade, se estiver aguardando o clique.
Exemplo:
fonte
Eu tentei algumas coisas, mas eu sou incapaz de fazê-lo sem
async/await
. Porque, se não o usarmos, ele causaDeadLock
ou a interface do usuário está bloqueada e, em seguida, podemos ativar aGrid_Click
entrada.fonte
Você pode bloquear assincronamente usando um
SemaphoreSlim
:Você não pode nem deseja bloquear o encadeamento do expedidor de forma síncrona, porque nunca será capaz de manipular o clique no
Grid
, ou seja, não pode ser bloqueado nem manipular eventos ao mesmo tempo.fonte
Console.Readline
blocks , ou seja, ele não retorna até que uma linha seja lida. SeuPickPoint
método não. Retorna imediatamente. Poderia bloquear potencialmente, mas você não conseguirá lidar com a entrada da interface do usuário enquanto eu escrevi na minha resposta. Em outras palavras, você precisaria manipular o clique dentro do método para obter o mesmo comportamento.PickPoint
que lide com os eventos do mouse. Não consigo ver para onde você está indo com isso?Tecnicamente, é possível com
AutoResetEvent
e semasync/await
, mas há uma desvantagem significativa:A desvantagem: se você chamar esse método diretamente em um manipulador de eventos de botão como o seu código de exemplo, ocorrerá um conflito e você verá que o aplicativo para de responder. Como você está usando o único thread da interface do usuário para aguardar o clique do usuário, ele não pode responder à ação de nenhum usuário, incluindo o clique do usuário na grade.
Os consumidores do método devem invocá-lo em outro encadeamento para evitar conflitos. Se pode ser garantido, tudo bem. Caso contrário, você precisará invocar o método como este:
Isso pode causar mais problemas para os consumidores da sua API, exceto se eles costumavam gerenciar seus próprios threads. Por isso
async/await
foi inventado.fonte
Eu acho que a questão está no próprio design. Se sua API funcionar em um elemento específico, ela deverá ser usada no manipulador de eventos desse mesmo elemento, não em outro elemento.
Por exemplo, aqui queremos obter a posição do evento click na grade, a API precisa ser usada no manipulador de eventos associado ao evento no elemento Grid, não no elemento button.
Agora, se o requisito é manipular o clique na Grade somente depois que clicarmos no Botão, a responsabilidade do Botão será adicionar o manipulador de eventos à Grade, e o evento clique na Grade mostrará a caixa de mensagem e removerá este manipulador de eventos adicionado pelo botão para que não seja mais acionado após esse clique ... (não é necessário bloquear o thread da interface do usuário)
Apenas para dizer que, se você bloquear o thread da interface do usuário no clique do botão, acho que o thread da interface do usuário não poderá acionar o evento click na grade posteriormente.
fonte
Primeiro de tudo, o thread da interface do usuário não pode ser bloqueado como a resposta que você recebeu da sua pergunta inicial.
Se você pode concordar com isso, é possível evitar async / wait para fazer com que seu cliente faça menos modificações, e nem precisa de multithreading.
Mas se você deseja bloquear o thread da interface do usuário ou o "fluxo de código", a resposta será que é impossível. Porque se o encadeamento da interface do usuário foi bloqueado, não há mais entrada a ser recebida.
Já que você mencionou sobre o aplicativo de console, eu apenas faço uma explicação simples.
Quando você executa um aplicativo de console ou chama
AllocConsole
de um processo que não foi anexado a nenhum console (janela), o conhost.exe, que pode fornecer um console (janela), é executado e o processo do aplicativo ou chamador de console é anexado ao console ( janela).Portanto, qualquer código que você escreva que possa bloquear o encadeamento do chamador, como
Console.ReadKey
não bloqueará o encadeamento da interface do usuário da janela do console, esse é o motivo pelo qual o aplicativo do console aguarda sua entrada, mas ainda pode responder a outras entradas, como clicar com o mouse.fonte