No livro Programming C #, há alguns exemplos de código sobre SynchronizationContext
:
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
Como sou iniciante em tópicos, responda em detalhes. Primeiro, não sei o que significa contexto, o que o programa salva no originalContext
? E quando o Post
método é acionado, o que o thread da interface do usuário fará?
Se eu perguntar algumas coisas tolas, por favor, corrija-me, obrigado!
EDIT: Por exemplo, e se eu apenas escrever myTextBox.Text = text;
no método, qual é a diferença?
c#
.net
multithreading
cloudyFan
fonte
fonte
async
/await
confia emSynchronizationContext
baixo.Respostas:
Simplificando,
SynchronizationContext
representa um local "onde" o código pode ser executado. Os delegados que são passados para o métodoSend
ou serão então chamados nesse local. ( é a versão sem bloqueio / assíncrona de .)Post
Post
Send
Cada encadeamento pode ter uma
SynchronizationContext
instância associada a ele. O fio condutor pode ser associada a um contexto de sincronização chamando o estáticoSynchronizationContext.SetSynchronizationContext
método eo contexto atual do segmento em execução pode ser consultado através daSynchronizationContext.Current
propriedade .Apesar do que acabei de escrever (cada segmento com um contexto de sincronização associado), a
SynchronizationContext
não representa necessariamente um segmento específico ; ele também pode encaminhar a chamada dos delegados transmitidos a ele para qualquer um dos vários threads (por exemplo, para umThreadPool
thread de trabalho) ou (pelo menos em teoria) para um núcleo específico da CPU ou mesmo para outro host de rede . O local em que seus delegados acabam executando depende do tipo deSynchronizationContext
uso.O Windows Forms instalará um
WindowsFormsSynchronizationContext
no thread no qual o primeiro formulário é criado. (Esse thread geralmente é chamado de "o thread da interface do usuário".) Esse tipo de contexto de sincronização chama os delegados transmitidos a ele exatamente nesse thread. Isso é muito útil, pois o Windows Forms, como muitas outras estruturas de interface do usuário, permite apenas a manipulação de controles no mesmo encadeamento no qual eles foram criados.O código para o qual você passou
ThreadPool.QueueUserWorkItem
será executado em um thread de trabalho do conjunto de threads. Ou seja, ele não será executado no thread em que vocêmyTextBox
foi criado, portanto, o Windows Forms, mais cedo ou mais tarde (especialmente nas versões do Release) lançará uma exceção, informando que você não pode acessarmyTextBox
através de outro thread.É por isso que você precisa, de alguma forma, "voltar" do segmento de trabalho para o "segmento de interface do usuário" (onde
myTextBox
foi criado) antes dessa atribuição específica. Isto se faz do seguinte modo:Enquanto você ainda estiver no thread da interface do usuário, capture o Windows Forms '
SynchronizationContext
lá e armazene uma referência a ele em uma variável (originalContext
) para uso posterior. Você deve consultarSynchronizationContext.Current
neste momento; se você o consultou dentro do código passadoThreadPool.QueueUserWorkItem
, poderá obter qualquer contexto de sincronização associado ao segmento de trabalho do conjunto de encadeamentos. Depois de armazenar uma referência ao contexto do Windows Forms, você pode usá-la em qualquer lugar e a qualquer momento para "enviar" o código ao thread da interface do usuário.Sempre que você precisar manipular um elemento da interface do usuário (mas não estiver mais ou não estiver no encadeamento da interface do usuário), acesse o contexto de sincronização do Windows Forms por meio de
originalContext
e entregue o código que manipulará a interface do usuário para umSend
ou outroPost
.Considerações finais e dicas:
O que os contextos de sincronização não farão por você é dizer qual código deve ser executado em um local / contexto específico e qual código pode ser executado normalmente normalmente, sem passar para a
SynchronizationContext
. Para decidir isso, você deve conhecer as regras e os requisitos da estrutura em que está programando - Windows Forms nesse caso.Portanto, lembre-se desta regra simples para Windows Forms: NÃO acesse controles ou formulários de um thread diferente daquele que os criou. Se você deve fazer isso, use o
SynchronizationContext
mecanismo conforme descrito acima ouControl.BeginInvoke
(que é uma maneira específica de fazer o Windows Forms de fazer exatamente a mesma coisa).Se você está programando contra .NET 4.5 ou posterior, você pode tornar sua vida muito mais fácil através da conversão de seu código que explicitamente usos
SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
, etc. para as novasasync
/await
palavras-chave e a biblioteca paralela de tarefas (TPL) , ou seja, a API circundante as classesTask
eTask<TResult>
. Eles, em um nível muito alto, cuidarão da captura do contexto de sincronização do encadeamento da interface do usuário, iniciando uma operação assíncrona e voltando ao encadeamento da interface do usuário para que você possa processar o resultado da operação.fonte
Application.Run
IIRC). Este é um tópico bastante avançado e não algo feito casualmente.null
) ou é uma instânciaSynchronizationContext
(ou uma subclasse dele). O objetivo dessa citação não era o que você obtinha, mas o que não obtinha: o contexto de sincronização do thread da interface do usuário.Eu gostaria de adicionar a outras respostas,
SynchronizationContext.Post
apenas enfileirar um retorno de chamada para execução posterior no segmento de destino (normalmente durante o próximo ciclo do loop de mensagens do segmento de destino) e, em seguida, a execução continuará no segmento de chamada. Por outro lado,SynchronizationContext.Send
tenta executar o retorno de chamada no thread de destino imediatamente, o que bloqueia o thread de chamada e pode resultar em conflito. Nos dois casos, existe a possibilidade de reentrada de código (inserir um método de classe no mesmo encadeamento de execução antes que a chamada anterior ao mesmo método retorne).Se você estiver familiarizado com o modelo de programação Win32, seria uma analogia muito próxima
PostMessage
e asSendMessage
APIs, que você pode chamar para enviar uma mensagem de um thread diferente daquele da janela de destino.Aqui está uma explicação muito boa de quais são os contextos de sincronização: É tudo sobre o SynchronizationContext .
fonte
Ele armazena o provedor de sincronização, uma classe derivada de SynchronizationContext. Nesse caso, provavelmente será uma instância do WindowsFormsSynchronizationContext. Essa classe usa os métodos Control.Invoke () e Control.BeginInvoke () para implementar os métodos Send () e Post (). Ou pode ser DispatcherSynchronizationContext, usa Dispatcher.Invoke () e BeginInvoke (). Em um aplicativo WinForms ou WPF, esse provedor é instalado automaticamente assim que você cria uma janela.
Quando você executa o código em outro encadeamento, como o encadeamento de conjunto de encadeamentos usado no snippet, é necessário ter cuidado para não usar diretamente objetos que não são seguros. Como qualquer objeto de interface do usuário, você deve atualizar a propriedade TextBox.Text do thread que criou o TextBox. O método Post () garante que o destino delegado seja executado nesse segmento.
Tenha em atenção que este fragmento é um pouco perigoso, só funcionará corretamente quando você o chamar do thread da interface do usuário. SynchronizationContext.Current possui valores diferentes em diferentes segmentos. Somente o thread da interface do usuário tem um valor utilizável. E é a razão pela qual o código teve que copiá-lo. Uma maneira mais legível e segura de fazer isso, em um aplicativo Winforms:
O que tem a vantagem de funcionar quando chamado de qualquer thread. A vantagem de usar SynchronizationContext.Current é que ainda funciona se o código é usado no Winforms ou no WPF, é importante em uma biblioteca. Esse certamente não é um bom exemplo desse código; você sempre sabe que tipo de TextBox você tem aqui; portanto, sempre sabe se deve usar Control.BeginInvoke ou Dispatcher.BeginInvoke. Na verdade, usar SynchronizationContext.Current não é tão comum.
O livro está tentando ensiná-lo sobre a segmentação, portanto, usar este exemplo defeituoso é aceitável. Na vida real, nos poucos casos em que você pode considerar usar SynchronizationContext.Current, você ainda pode deixar as palavras-chave assíncronas / em espera do C # ou TaskScheduler.FromCurrentSynchronizationContext () para fazer isso por você. Mas observe que eles ainda se comportam mal da maneira que o trecho faz quando você os usa no encadeamento errado, pelo mesmo motivo. Uma pergunta muito comum por aqui, o nível extra de abstração é útil, mas torna mais difícil descobrir por que eles não funcionam corretamente. Espero que o livro também diga quando não usá-lo :)
fonte
O objetivo do contexto de sincronização aqui é garantir que isso
myTextbox.Text = text;
seja chamado no thread principal da interface do usuário.O Windows exige que os controles da GUI sejam acessados apenas pelo thread com o qual foram criados. Se você tentar atribuir o texto em um encadeamento em segundo plano sem primeiro sincronizar (por qualquer um dos vários meios, como este ou o padrão Invoke), uma exceção será lançada.
O que isso faz é salvar o contexto de sincronização antes de criar o encadeamento em segundo plano e, em seguida, o encadeamento em segundo plano usa o método context.Post, execute o código da GUI.
Sim, o código que você mostrou é basicamente inútil. Por que criar um thread em segundo plano, apenas para voltar imediatamente ao thread principal da interface do usuário? É apenas um exemplo.
fonte
Para a fonte
Por exemplo: Suponha que você tenha dois threads, Thread1 e Thread2. Digamos, o Thread1 está fazendo algum trabalho e o Thread1 deseja executar o código no Thread2. Uma maneira possível de fazer isso é solicitar ao objeto SynchronizationContext do Thread2, fornecê-lo ao Thread1 e, em seguida, o Thread1 pode chamar SynchronizationContext.Send para executar o código no Thread2.
fonte
SynchronizationContext fornece uma maneira de atualizar uma interface do usuário de um thread diferente (de forma síncrona pelo método Send ou de forma assíncrona pelo método Post).
Veja o seguinte exemplo:
SynchronizationContext.Current retornará o contexto de sincronização do thread da interface do usuário. Como eu sei disso? No início de todos os formulários ou aplicativos WPF, o contexto será definido no thread da interface do usuário. Se você criar um aplicativo WPF e executar o meu exemplo, verá que, quando clica no botão, ele dorme por aproximadamente 1 segundo e mostra o conteúdo do arquivo. Você pode esperar que não, porque o chamador do método UpdateTextBox (que é Work1) é um método passado para um Thread; portanto, ele deve adormecer esse thread, não o thread principal da interface do usuário, NOPE! Mesmo que o método Work1 seja passado para um thread, observe que ele também aceita um objeto que é o SyncContext. Se você observar, verá que o método UpdateTextBox é executado pelo método syncContext.Post e não pelo método Work1. Veja o seguinte:
O último exemplo e este executam o mesmo. Ambos não bloqueiam a interface do usuário enquanto o fazem.
Em conclusão, pense em SynchronizationContext como um encadeamento. Não é um segmento, ele define um segmento (observe que nem todos os segmentos têm um SyncContext). Sempre que chamamos o método Post ou Send para atualizar uma interface do usuário, é como atualizar a interface do usuário normalmente a partir do thread principal da interface do usuário. Se, por algum motivo, você precisar atualizar a interface do usuário a partir de um thread diferente, verifique se o thread possui o SyncContext do thread principal da interface do usuário e chame o método Send ou Post com o método que você deseja executar e pronto. conjunto.
Espero que isso ajude você, companheiro!
fonte
O SynchronizationContext é basicamente um provedor de execução de delegados de retorno de chamada, responsável principalmente por garantir que os delegados sejam executados em um determinado contexto de execução após uma parte específica do código (incluída no Objeto de tarefa do .Net TPL) de um programa concluir sua execução.
Do ponto de vista técnico, o SC é uma classe C # simples, orientada para oferecer suporte e fornecer sua função especificamente para objetos da Biblioteca Paralela de Tarefas.
Todo aplicativo .Net, exceto os aplicativos de console, possui uma implementação específica dessa classe com base na estrutura subjacente específica, ou seja: WPF, WindowsForm, Asp Net, Silverlight, etc.
A importância desse objeto está vinculada à sincronização entre resultados retornados da execução assíncrona do código e a execução do código dependente que está aguardando resultados desse trabalho assíncrono.
E a palavra "contexto" significa contexto de execução, que é o contexto de execução atual em que o código em espera será executado, ou seja, a sincronização entre o código assíncrono e o código em espera ocorre em um contexto de execução específico, portanto, esse objeto é chamado SynchronizationContext: representa o contexto de execução que cuidará da sincronização do código assíncrono e da execução do código em espera .
fonte
Este exemplo é dos exemplos de Joseph Albahari no Linqpad, mas realmente ajuda a entender o que o contexto de Sincronização faz.
fonte