Os cronômetros de C # decorrem em um segmento separado?

97

Um System.Timers.Timer decorre em um thread separado do thread que o criou?

Digamos que eu tenha uma aula com um cronômetro que dispara a cada 5 segundos. Quando o cronômetro dispara, no método decorrido, algum objeto é modificado. Vamos dizer que leva muito tempo para modificar este objeto, como 10 segundos. É possível que eu tenha colisões de threads neste cenário?

user113164
fonte
Isso pode causar problemas. Observe que, em geral, os threads de threadpool não são projetados para processos de longa execução.
Greg D
Eu estava correndo para isso, testando com um serviço do Windows. O que funcionou para mim foi desabilitar o cronômetro como a primeira instrução no evento OnTimer, realizar minhas tarefas e habilitar o cronômetro no final. Isso funcionou de forma confiável em um ambiente de produção por algum tempo.
Steve

Respostas:

60

Para System.Timers.Timer :

Veja a resposta de Brian Gideon abaixo

Para System.Threading.Timer :

A documentação do MSDN sobre timers afirma:

A classe System.Threading.Timer faz callbacks em um thread ThreadPool e não usa o modelo de evento.

Então, de fato, o cronômetro passa em um segmento diferente.

Joren
fonte
21
Verdade, mas essa é uma classe completamente diferente. O OP perguntou sobre a classe System.Timers.Timer.
Brian Gideon,
1
Oh, você está certo. msdn.microsoft.com/en-us/library/system.timers.timer.aspx diz "O evento Elapsed é gerado em um thread ThreadPool." A mesma conclusão daí, suponho.
Joren,
6
Bem, sim, mas não é tão simples. Veja minha resposta.
Brian Gideon,
192

Depende. O System.Timers.Timerpossui dois modos de operação.

Se SynchronizingObjectfor definido como uma ISynchronizeInvokeinstância, o Elapsedevento será executado no thread que hospeda o objeto de sincronização. Normalmente, essas ISynchronizeInvokeinstâncias nada mais são do que instâncias antigas Controle com as Formquais estamos todos familiarizados. Portanto, nesse caso, o Elapsedevento é invocado no thread de IU e se comporta de forma semelhante ao System.Windows.Forms.Timer. Caso contrário, realmente depende da ISynchronizeInvokeinstância específica que foi usada.

Se SynchronizingObjectfor nulo, o Elapsedevento é chamado em um ThreadPoolencadeamento e se comporta de forma semelhante ao System.Threading.Timer. Na verdade, ele realmente usa um System.Threading.Timernos bastidores e faz a operação de empacotamento depois de receber o retorno de chamada do cronômetro, se necessário.

Brian Gideon
fonte
4
Se você quiser que o retorno de chamada do timer seja executado em um novo encadeamento, você deve usar um System.Threading.Timerou System.Timers.Timer?
CJ7,
1
@ cj7: Qualquer um pode fazer isso.
Brian Gideon,
e se eu tiver uma lista do tipo complexo (pessoa) e quiser ter um tempo dentro de cada pessoa? Preciso que isso seja executado no mesmo thread (todas as pessoas), pois se chamar o método de primeira pessoa, a segunda pessoa deve esperar até que a primeira termine o evento decorrido. Posso fazer isso?
Leandro De Mello Fagundes
4
Todo mundo ... System.Timers.Timertem dois modos de operação. Ele pode ser executado em um thread de pool de threads atribuído aleatoriamente OU pode ser executado em qualquer thread que esteja hospedando a ISynchronizeInvokeinstância. Não sei como deixar isso mais claro. System.Threading.Timertem pouco (ou nada) a ver com a pergunta original.
Brian Gideon de
@LeandroDeMelloFagundes Você não pode usar lockpara isso?
Ozkan
24

Cada evento decorrido será disparado no mesmo encadeamento, a menos que um Elapsed anterior ainda esteja em execução.

Então ele lida com a colisão para você

tente colocar isso em um console

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

você vai conseguir algo assim

10
6
12
6
12

onde 10 é o encadeamento de chamada e 6 e 12 estão disparando do evento bg decorrido. Se você remover o Thread.Sleep (2000); você vai conseguir algo assim

10
6
6
6
6

Uma vez que não há colisões.

Mas isso ainda deixa você com um problema. se você está disparando o evento a cada 5 segundos e leva 10 segundos para editar, você precisa de algum bloqueio para pular algumas edições.

Simon
fonte
8
Adicionar um timer.Stop()no início do método de evento Elapsed e, em seguida, um timer.Start()no final do método de evento Elapsed evitará que o evento Elapsed colida.
Metro Smurf de
4
Você não precisa colocar timer.Stop (), você só precisa definir timer.AutoReset = false; então você faz timer.Start () após processar o evento. Acho que essa é a melhor maneira de evitar colisões.
João Antunes
17

Para System.Timers.Timer, em thread separado, se SynchronizingObject não estiver definido.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Saída que você veria se DummyTimer disparasse em um intervalo de 5 segundos:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

Portanto, como visto, OnDummyTimerFired é executado no thread Workers.

Não, complicação adicional - Se você reduzir o intervalo para 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

Isso ocorre porque se a execução anterior de OnDummyTimerFired não for feita quando o próximo tique for disparado, o .NET criará um novo encadeamento para fazer esse trabalho.

Para complicar ainda mais, "a classe System.Timers.Timer fornece uma maneira fácil de lidar com esse dilema - ela expõe uma propriedade pública SynchronizingObject. Definir essa propriedade para uma instância de um formulário do Windows (ou um controle em um formulário do Windows) garantirá que o código em seu manipulador de eventos Elapsed é executado no mesmo thread em que o SynchronizingObject foi instanciado. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2

Swab.Jat
fonte
É um bom exemplo. Eu não sabia até agora e preciso definir algumas travas aqui e ali. Perfeito.
Olaru Mircea,
E se eu quiser que o cronômetro dispare o evento Elapsed no thread principal de um aplicativo de console?
jacktric
13

Se o evento decorrido demorar mais que o intervalo, será criado outro encadeamento para gerar o evento decorrido. Mas há uma solução alternativa para isso

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Rajan
fonte
+1 resposta simples e limpa, mas isso nem sempre funcionará. Em vez disso, deve-se usar a propriedade de bloqueio ou SynchronizingObject do temporizador.
RollerCosta