Pergunta

Eu implementado da seguinte discussão processamento em segundo plano, onde Jobs é um Queue<T>:

static void WorkThread()
{
    while (working)
    {
        var job;

        lock (Jobs)
        {
            if (Jobs.Count > 0)
                job = Jobs.Dequeue();
        }

        if (job == null)
        {
            Thread.Sleep(1);
        }
        else
        {
            // [snip]: Process job.
        }
    }
}

Isso produziu um atraso perceptível entre quando os trabalhos estavam sendo introduzido e quando eles estavam realmente começando a ser executado (lotes de tarefas são inseridas ao mesmo tempo, e cada trabalho é apenas [relativamente] pequena). O atraso não foi uma enorme, mas eu comecei a pensar sobre o problema, e fez a seguinte alteração:

static ManualResetEvent _workerWait = new ManualResetEvent(false);
// ...
    if (job == null)
    {
        lock (_workerWait)
        {
            _workerWait.Reset();
        }
        _workerWait.WaitOne();
    }

Onde o fio acrescentando empregos agora bloqueia _workerWait e chamadas _workerWait.Set() quando ele é feito acrescentando empregos. Esta solução (aparentemente) instantaneamente começa a processar empregos, e o atraso se foi completamente.

A minha pergunta é, em parte, "Por que isso acontece?", Concedido que Thread.Sleep(int) pode muito bem dormir por mais tempo do que você especificar, e em parte "Como é que o ManualResetEvent atingir esse nível de desempenho?".

EDIT: Desde alguém perguntou sobre a função que está na fila itens, aqui está, juntamente com o sistema completo, tal como está no momento

.
public void RunTriggers(string data)
{
    lock (this.SyncRoot)
    {
        this.Triggers.Sort((a, b) => { return a.Priority - b.Priority; });

        foreach (Trigger trigger in this.Triggers)
        {
            lock (Jobs)
            {
                Jobs.Enqueue(new TriggerData(this, trigger, data));
                _workerWait.Set();
            }
        }
    }
}

static private ManualResetEvent _workerWait = new ManualResetEvent(false);
static void WorkThread()
{
    while (working)
    {
        TriggerData job = null;

        lock (Jobs)
        {
            if (Jobs.Count > 0)
                job = Jobs.Dequeue();

            if (job == null)
            {
                _workerWait.Reset();
            }
        }

        if (job == null)
            _workerWait.WaitOne();
        else
        {
            try
            {
                foreach (Match m in job.Trigger.Regex.Matches(job.Data))
                    job.Trigger.Value.Action(job.World, m);
            }
            catch (Exception ex)
            {
                job.World.SendLineToClient("\r\n\x1B[32m -- {0} in trigger ({1}): {2}\x1B[m",
                    ex.GetType().ToString(), job.Trigger.Name, ex.Message);
            }
        }
    }
}
Foi útil?

Solução

Os eventos são primitivas do kernel fornecidos pelo OS / Kernel que é projetado apenas para este tipo de coisas. O kernel fornece um limite sobre o qual você pode garantir operações atômicas que é importante para a sincronização (Alguns atomicity pode ser feito no espaço do usuário também com suporte de hardware).

Em suma, quando um thread espera em um evento ele é colocado em uma lista de espera para esse evento e marcados como não-executável. Quando o evento é sinalizado, o kernel acorda os na lista de espera e os marca como executável e eles podem continuar a executar. É, naturalmente, um enorme benefício que uma thread pode acordar imediatamente quando o evento é sinalizado, vs dormir por um longo tempo e verifique novamente a condição de vez em quando.

Mesmo um milésimo de segundo é realmente um tempo muito longo, você poderia ter processado milhares de evento em que o tempo. Também a resolução de tempo é tradicionalmente 10ms, então dormir menos do que 10ms geralmente apenas resultados em 10ms dormir de qualquer maneira. Com um evento, um thread pode ser acordado e agendada imediatamente

Outras dicas

Primeiro de bloqueio na _workerWait é inútil, um evento é um sistema (kernel) objeto projetado para sinalização entre threads (e muito utilizada na API Win32 para operações assíncronas). Por isso, é bastante seguro para vários segmentos para definir ou redefini-la sem sincronização adicional.

Quanto à sua pergunta principal, necessidade de ver a lógica para colocar as coisas na fila, bem como, e algumas informações sobre o quanto o trabalho é feito para cada trabalho (é o segmento de trabalho gastando mais trabalho de processamento de tempo ou em espera para o trabalho ).

Provavelmente a melhor solução seria a utilização de uma instância de objecto para bloqueio e sobre o uso Monitor.Pulse e Monitor.Wait como uma variável de estado.

Edit: Com vista para o código para enqueue, parece que a resposta # 1116297 tem direito:. um atraso de 1 ms é muito tempo para esperar, dado que muitos dos itens de trabalho será extremamente rápida de processo

A abordagem de ter um mecanismo para acordar o segmento de trabalho é correcto (como não há fila .NET concomitante com uma operação Desenfileiramento de bloqueio). No entanto, em vez de usar um evento, uma variável de condição vai ser um pouco mais eficiente (como nos casos não sustentou que não requer uma transição kernel):

object sync = new Object();
var queue = new Queue<TriggerData>();

public void EnqueueTriggers(IEnumerable<TriggerData> triggers) {
  lock (sync) {
    foreach (var t in triggers) {
      queue.Enqueue(t);
    }
    Monitor.Pulse(sync);  // Use PulseAll if there are multiple worker threads
  }
}

void WorkerThread() {
  while (!exit) {
    TriggerData job = DequeueTrigger();
    // Do work
  }
}

private TriggerData DequeueTrigger() {
  lock (sync) {
    if (queue.Count > 0) {
      return queue.Dequeue();
    }
    while (queue.Count == 0) {
      Monitor.Wait(sync);
    }
    return queue.Dequeue();
  }
}

Monitor.Wait irá liberar o bloqueio do parâmetro, espere até que Pulse() ou PulseAll() é chamado contra a fechadura, em seguida, re-entrar na fechadura e retorno. Necessidade de verificar novamente a condição de espera porque outra thread pode ter lido o item fora da fila.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top