Eu já vi, em muitos lugares, que é uma sabedoria canônica 1 que é responsabilidade do chamador garantir que você esteja no encadeamento da interface do usuário ao atualizar os componentes da interface do usuário (especificamente, no Java Swing, que você está no encadeamento de distribuição de eventos ) .
Porque isto é assim? O Event Dispatch Thread é uma preocupação da visualização no MVC / MVP / MVVM; para manipulá-lo em qualquer lugar, mas a visualização cria um forte acoplamento entre a implementação da visualização e o modelo de encadeamento da implementação dessa visualização.
Especificamente, digamos que eu tenho um aplicativo arquitetado MVC que usa o Swing. Se o chamador for responsável por atualizar os componentes no thread de distribuição de eventos, se eu tentar trocar minha implementação do Swing View por uma implementação JavaFX, devo alterar todo o código do apresentador / controlador para usar o thread do aplicativo JavaFX .
Então, suponho que tenho duas perguntas:
- Por que é responsabilidade do chamador garantir a segurança do encadeamento do componente da interface do usuário? Onde está a falha no meu raciocínio acima?
- Como posso projetar meu aplicativo para ter um acoplamento frouxo dessas preocupações de segurança de rosca, e ainda assim ser adequadamente seguro de rosca?
Deixe-me adicionar um código Java do MCVE para ilustrar o que quero dizer com "responsável pela chamada" (existem outras boas práticas aqui que não estou praticando, mas estou tentando, de propósito, ser o mínimo possível):
Chamador sendo responsável:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
view.setData(data);
}
});
}
}
public class View {
void setData(Data data) {
component.setText(data.getMessage());
}
}
Ver ser responsável:
public class Presenter {
private final View;
void updateViewWithNewData(final Data data) {
view.setData(data);
}
}
public class View {
void setData(Data data) {
EventQueue.invokeLater(new Runnable() {
public void run() {
component.setText(data.getMessage());
}
});
}
}
1: O autor dessa postagem tem a maior pontuação de tag no Swing no estouro de pilha. Ele diz isso em todo o lugar e também vi que é responsabilidade do interlocutor em outros lugares.
fonte
Respostas:
No final de seu ensaio de sonho fracassado , Graham Hamilton (um grande arquiteto Java) menciona se os desenvolvedores "devem preservar a equivalência com um modelo de fila de eventos, eles precisam seguir várias regras não óbvias" e ter uma visibilidade visível e explícita. o modelo de fila de eventos "parece ajudar as pessoas a seguir o modelo com mais confiabilidade e, assim, construir programas GUI que funcionam de maneira confiável".
Em outras palavras, se você tentar colocar uma fachada multithread no topo de um modelo de fila de eventos, a abstração ocasionalmente vazará de maneiras não óbvias e extremamente difíceis de depurar. Parece que vai funcionar no papel, mas acaba caindo aos pedaços na produção.
Adicionar pequenos wrappers em torno de componentes únicos provavelmente não será problemático, como atualizar uma barra de progresso de um thread de trabalho. Se você tentar fazer algo mais complexo que exija vários bloqueios, ficará muito difícil entender como a camada multithread e a camada da fila de eventos interagem.
Observe que esses tipos de problemas são universais em todos os kits de ferramentas da GUI. Presumir que um modelo de despacho de evento no seu apresentador / controlador não o acopla firmemente a apenas um modelo de simultaneidade de um kit de ferramentas da GUI específico, ele o acopla a todos eles . A interface da fila de eventos não deve ser tão difícil de abstrair.
fonte
Porque tornar o thread lib da GUI seguro é uma dor de cabeça enorme e um gargalo.
O fluxo de controle nas GUIs geralmente segue duas direções da fila de eventos para a janela raiz, para os widgets da GUI e do código do aplicativo para o widget propagado até a janela raiz.
É difícil conceber uma estratégia de bloqueio que não bloqueie a janela raiz (causará muita contenção) . Travar de baixo para cima enquanto outro segmento estiver travando de cima para baixo é uma ótima maneira de obter um impasse instantâneo.
E verificar se o encadeamento atual é o encadeamento da GUI sempre que custa caro e pode causar confusão sobre o que realmente está acontecendo com a GUI, especialmente quando você está executando uma sequência de gravação de atualização de leitura. Isso precisa ter um bloqueio nos dados para evitar corridas.
fonte
A threadedness (em um modelo de memória compartilhada) é uma propriedade que tende a desafiar os esforços de abstração. Um exemplo simples é a
Set
do tipo: enquantoContains(..)
eAdd(...)
eUpdate(...)
é uma API perfeitamente válida em um único cenário de rosca, o cenário multi-threaded precisa de umAddOrUpdate
.O mesmo se aplica à interface do usuário - se você deseja exibir uma lista de itens com uma contagem dos itens no topo da lista, é necessário atualizar os dois a cada alteração.
Nada disso parece realmente tentador. Recomenda-se responsabilizar o apresentador pelo manuseio da segmentação, porque não é bom, mas porque funciona e as alternativas são piores.
fonte
System.Windows.Threading.Dispatcher
manipular o envio para os segmentos de interface do usuário WPF e WinForms. Esse tipo de camada entre o apresentador e a visualização é definitivamente útil. Mas ele apenas fornece independência ao kit de ferramentas, não enfraquece a independência.Há algum tempo, li um blog muito bom que discute esse problema (mencionado por Karl Bielefeldt), o que basicamente diz é que é muito perigoso tentar tornar o thread do kit de interface do usuário seguro, pois apresenta possíveis bloqueios e depende de como implementadas, condições de corrida no quadro.
Há também uma consideração de desempenho. Agora não muito, mas quando o Swing foi lançado, ele foi fortemente criticado por seu desempenho (ruim), mas isso não foi culpa do Swing, era falta de conhecimento das pessoas sobre como usá-lo.
O SWT aplica o conceito de segurança de encadeamento lançando exceções se você violá-lo, não é bonito, mas pelo menos você está ciente disso.
Se você observar o processo de pintura, por exemplo, a ordem na qual os elementos são pintados é muito importante. Você não deseja que a pintura de um componente tenha efeito colateral em qualquer outra parte da tela. Imagine se você pudesse atualizar a propriedade de texto de uma etiqueta, mas ela fosse pintada por dois segmentos diferentes, e você poderia acabar com uma saída corrompida. Portanto, toda a pintura é feita em um único encadeamento, normalmente com base na ordem de requisitos / solicitações (mas às vezes condensada para reduzir o número de ciclos físicos reais de pintura)
Você mencionou a mudança do Swing para o JavaFX, mas você teria esse problema com praticamente qualquer estrutura de interface do usuário (não apenas com clientes grossos, mas também com a Web). O Swing parece ser o que destaca o problema.
Você pode projetar uma camada intermediária (um controlador de um controlador?), Cuja tarefa é garantir que as chamadas para a interface do usuário sejam sincronizadas corretamente. É impossível saber exatamente como você pode projetar partes da API que não são da interface do usuário da perspectiva da API da interface do usuário e a maioria dos desenvolvedores reclamaria que qualquer proteção de thread implementada na API da interface do usuário era restritiva ou não atendia às suas necessidades. Melhor permitir que você decida como deseja resolver esse problema, com base em suas necessidades
Um dos maiores problemas que você precisa considerar é a capacidade de justificar uma determinada ordem de eventos, com base em informações conhecidas. Por exemplo, se o usuário redimensionar a janela, o modelo da Fila de Eventos garante que uma determinada ordem de eventos ocorrerá, isso pode parecer simples, mas se a fila permitir que eventos sejam acionados por outros encadeamentos, você não poderá mais garantir a ordem em em que os eventos podem ocorrer (uma condição de corrida) e, de repente, você precisa começar a se preocupar com diferentes estados e não fazer nada até que algo mais aconteça. Você começa a compartilhar bandeiras do estado e acaba com espaguete.
Ok, você pode resolver isso tendo algum tipo de fila que ordenou os eventos com base no horário em que foram emitidos, mas não é isso que já temos? Além disso, você ainda não pode garantir que o segmento B gere seus eventos DEPOIS do segmento A
A principal razão pela qual as pessoas ficam chateadas por ter que pensar em seu código é porque elas são feitas para pensar em seu código / design. "Por que não pode ser mais simples?" Não pode ser mais simples, porque não é um problema simples.
Lembro-me de quando o PS3 foi lançado e a Sony estava conversando com o processador Cell e sua capacidade de executar linhas separadas de lógica, decodificar áudio, vídeo, carregar e resolver dados do modelo. Um desenvolvedor de jogos perguntou: "Isso é incrível, mas como você sincroniza os fluxos?"
O problema de que o desenvolvedor estava falando era que, em algum momento, todos esses fluxos separados precisariam ser sincronizados em um único canal para saída. O pobre apresentador simplesmente deu de ombros, pois não era um problema que eles estavam familiarizados. Obviamente, eles tiveram soluções para resolver esse problema agora, mas ele tem graça na época.
Os computadores modernos estão recebendo muitas informações de vários lugares simultaneamente, todas essas informações precisam ser processadas e entregues ao usuário imediatamente, o que não interfere na apresentação de outras informações, por isso é um problema complexo, sem uma única solução simples.
Agora, tendo a capacidade de alternar estruturas, não é uma coisa fácil de projetar, mas, tome MVC por um momento, o MVC pode ser de várias camadas, ou seja, você pode ter um MVC que lida diretamente com o gerenciamento da estrutura da interface do usuário. poderia então envolver isso, novamente, em um MVC de camada superior que lida com interações com outras estruturas (potencialmente multiencadeadas), seria responsabilidade dessa camada determinar como a camada MVC inferior é notificada / atualizada.
Você usaria a codificação para fazer interface com padrões de design e padrões de fábrica ou construtor para construir essas diferentes camadas. Isso significa que as estruturas multithread são dissociadas da camada da interface do usuário através do uso de uma camada intermediária, como uma idéia.
fonte