NET: Como ter sinal discussão de fundo principais dados de segmento está disponível?
-
02-07-2019 - |
Pergunta
O que é a técnica adequada para ter ThreadA sinal de ThreadB de algum evento, sem ter ThreadB sentar bloqueados à espera de um evento acontecer?
i ter uma discussão de fundo que estará preenchendo uma lista compartilhada
i considerado definindo um evento com um objeto EventWaitHandle, mas eu não posso ter o meu thread principal sentado em uma Event.WaitOne ().
i considerada ter um callback delegado, mas a) eu não quero o segmento principal fazendo um trabalho na delegado: as necessidades de rosca para voltar ao trabalho adicionando mais coisas - eu não quero que ele espera, enquanto executa a delegar e b) as necessidades de delegado para ser empacotado para o segmento principal, mas eu não estou correndo uma interface de usuário, eu não tenho controle para .Invoke o delegado contra.
i considerada ter um callback delegado que simplesmente inicia uma System.Windows.Forms.Timer intervalo de zero (com acesso thread para o temporizador sincronizado). Desta forma, o segmento só precisa ser preso como ele chama
Timer.Enabled = true;
mas que parece ser um hack.
Nos tempos antigos meu objeto teria criado uma janela oculta e tinha as mensagens segmento pós para HWND que janelas escondidas. i considerada como criar um controle escondido, mas entendo que você não pode .Invoke em um controle sem alça criado. Além disso, eu não tenho nenhuma UI: meu objeto poderia ter sido criado em um servidor web, serviço ou console, eu não quero que haja um controle gráfico aparecer - nem quero compilar uma dependência System.Windows. formas.
i considerou ter meu objeto expor uma interface ISynchronizeInvoke, mas então eu teria que implementar .Invoke (), e esse é o meu problema.
O que é a técnica adequada para ter fio Um fio de sinal B de algum evento, sem ter segmento B sit bloqueados à espera de um evento acontecer?
Solução
Aqui está um exemplo de código para a classe System.ComponentModel.BackgroundWorker.
private static BackgroundWorker worker = new BackgroundWorker();
static void Main(string[] args)
{
worker.DoWork += worker_DoWork;
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
worker.ProgressChanged += worker_ProgressChanged;
worker.WorkerReportsProgress = true;
Console.WriteLine("Starting application.");
worker.RunWorkerAsync();
Console.ReadKey();
}
static void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine("Progress.");
}
static void worker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Starting doing some work now.");
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
worker.ReportProgress(i);
}
}
static void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Done now.");
}
Outras dicas
Estou combinando algumas respostas aqui.
A situação ideal usa uma bandeira thread-safe, como um AutoResetEvent
. Você não tem que bloquear indefinidamente quando você chamar WaitOne()
, na verdade ele tem uma sobrecarga que permite que você especifique um tempo limite. Essa sobrecarga retornos false
se o sinalizador não foi definido durante o intervalo.
A Queue
é uma estrutura mais ideal para um relacionamento produtor / consumidor, mas você pode imitá-los se suas exigências estão forçando você a usar um List
. A principal diferença é que você vai ter para garantir o seu acesso fechaduras de consumo para a coleção enquanto ele está extraindo itens; a coisa mais segura é provavelmente a usar o método CopyTo
para copiar todos os elementos para uma matriz, em seguida, liberar o bloqueio. É claro, garantir que o seu produtor não tentará atualizar o List
enquanto o bloqueio é mantido.
Aqui está uma simples C # aplicação de consola que demonstra como isso pode ser implementado. Se você brincar com os intervalos de tempo pode causar várias coisas aconteçam; nesta configuração particular que eu estava tentando ter o produtor gerar vários itens antes de os controlos de consumo para os itens.
using System;
using System.Collections.Generic;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
private static object LockObject = new Object();
private static AutoResetEvent _flag;
private static Queue<int> _list;
static void Main(string[] args)
{
_list = new Queue<int>();
_flag = new AutoResetEvent(false);
ThreadPool.QueueUserWorkItem(ProducerThread);
int itemCount = 0;
while (itemCount < 10)
{
if (_flag.WaitOne(0))
{
// there was an item
lock (LockObject)
{
Console.WriteLine("Items in queue:");
while (_list.Count > 0)
{
Console.WriteLine("Found item {0}.", _list.Dequeue());
itemCount++;
}
}
}
else
{
Console.WriteLine("No items in queue.");
Thread.Sleep(125);
}
}
}
private static void ProducerThread(object state)
{
Random rng = new Random();
Thread.Sleep(250);
for (int i = 0; i < 10; i++)
{
lock (LockObject)
{
_list.Enqueue(rng.Next(0, 100));
_flag.Set();
Thread.Sleep(rng.Next(0, 250));
}
}
}
}
}
Se você não deseja bloquear o produtor em tudo, é um pouco mais complicado. Neste caso, eu sugiro fazer o produtor a sua própria classe com tanto um privado e um buffer pública e uma AutoResetEvent
público. O produtor vai por itens de armazenamento padrão no buffer privado, em seguida, tentar escrevê-los para o buffer público. Quando o consumidor está trabalhando com o tampão público, ele redefine o sinalizador no objeto produtor. Antes das tentativas produtores para mover itens a partir do buffer privada para o buffer público, verifica esta bandeira e apenas copia itens quando o consumidor não está trabalhando nisso.
Se você usar um backgroundworker para iniciar a segunda linha e usar o evento ProgressChanged para notificar o outro segmento que dados estão prontos. Outros eventos também estão disponíveis. este artigo MSDN deve começar .
Existem muitas maneiras de fazer isso, dependendo exatamente o que você quer fazer. A produtor / consumidor fila é provavelmente o que você quer. Para um olhar excelente em profundidade em tópicos, consulte o capítulo sobre Enfiar (disponível online) do excelente livro C # 3.0 in a Nutshell .
Você pode usar um AutoResetEvent (ou ManualResetEvent). Se você usar AutoResetEvent.WaitOne (0, false), não irá bloquear. Por exemplo:
AutoResetEvent ev = new AutoResetEvent(false);
...
if(ev.WaitOne(0, false)) {
// event happened
}
else {
// do other stuff
}
O BackgroundWorker é a resposta neste caso. É a construção única enfiar que é capaz de mensagens de forma assíncrona enviar para o fio que criou o objeto BackgroundWorker. Internamente BackgroundWorker
usa a classe AsyncOperation
chamando o método asyncOperation.Post()
.
this.asyncOperation = AsyncOperationManager.CreateOperation(null);
this.asyncOperation.Post(delegateMethod, arg);
Algumas outras classes no .NET framework também usam AsyncOperation:
- BackgroundWorker
- SoundPlayer.LoadAsync ()
- SmtpClient.SendAsync ()
- Ping.SendAsync ()
- WebClient.DownloadDataAsync ()
- WebClient.DownloadFile ()
- WebClient.DownloadFileAsync ()
- WebClient ...
- PictureBox.LoadAsync ()
Se o seu thread "main" é a bomba de mensagem do Windows (GUI) da linha, então você pode pesquisar usando um Forms.Timer - ajustar o temporizador de intervalo de acordo com o quão rápido você precisa ter seu segmento GUI 'aviso' os dados do o segmento de trabalho.
Lembre-se de acesso de sincronização para o List<>
compartilhada se você está indo para uso foreach
, a exceções evitar CollectionModified
.
Eu uso essa técnica por todas as atualizações de GUI adaptadas ao mercado de-dados em um aplicativo de negociação em tempo real, e ele funciona muito bem.