¿Transcurren los temporizadores de C # en un hilo separado?

97

¿Un System.Timers.Timer transcurre en un hilo diferente al hilo que lo creó?

Digamos que tengo una clase con un temporizador que se activa cada 5 segundos. Cuando se dispara el temporizador, en el método transcurrido, se modifica algún objeto. Digamos que se necesita mucho tiempo para modificar este objeto, como 10 segundos. ¿Es posible que me encuentre con colisiones de subprocesos en este escenario?

usuario113164
fuente
Esto podría ocasionar problemas. Tenga en cuenta que, en general, los subprocesos de agrupación de subprocesos no están diseñados para procesos de larga ejecución.
Greg D
Me estaba encontrando con esto, probando con un servicio de Windows. Lo que funcionó para mí fue deshabilitar el temporizador como la primera instrucción en el evento OnTimer, realizar mis tareas y luego habilitar el temporizador al final. Esto ha funcionado de manera confiable en un entorno de producción durante algún tiempo.
Steve

Respuestas:

60

Para System.Timers.Timer :

Vea la respuesta de Brian Gideon a continuación

Para System.Threading.Timer :

La documentación de MSDN sobre temporizadores indica:

La clase System.Threading.Timer realiza devoluciones de llamada en un hilo ThreadPool y no usa el modelo de eventos en absoluto.

Entonces, de hecho, el temporizador transcurre en un hilo diferente.

Joren
fuente
21
Es cierto, pero esa es una clase completamente diferente. El OP preguntó sobre la clase System.Timers.Timer.
Brian Gideon
1
Oh, tienes razón. msdn.microsoft.com/en-us/library/system.timers.timer.aspx dice "El evento Elapsed se genera en un subproceso ThreadPool". La misma conclusión a partir de ahí, supongo.
Joren
6
Bueno, sí, pero no es tan simple. Mira mi respuesta.
Brian Gideon
192

Depende. El System.Timers.Timertiene dos modos de funcionamiento.

Si SynchronizingObjectse establece en una ISynchronizeInvokeinstancia, el Elapsedevento se ejecutará en el hilo que aloja el objeto de sincronización. Por lo general, estos ISynchronizeInvokecasos no son más que casos antiguos Controly con los Formque todos estamos familiarizados. Entonces, en ese caso, el Elapsedevento se invoca en el subproceso de la interfaz de usuario y se comporta de manera similar a System.Windows.Forms.Timer. De lo contrario, realmente depende de la ISynchronizeInvokeinstancia específica que se utilizó.

Si SynchronizingObjectes nulo, el Elapsedevento se invoca en un ThreadPoolhilo y se comporta de forma similar a System.Threading.Timer. De hecho, en realidad utiliza un System.Threading.Timerdetrás de escena y realiza la operación de clasificación después de recibir la devolución de llamada del temporizador si es necesario.

Brian Gideon
fuente
4
Si desea que la devolución de llamada del temporizador se ejecute en un nuevo hilo, ¿debería usar una System.Threading.Timero System.Timers.Timer?
CJ7
1
@ cj7: Cualquiera puede hacer eso.
Brian Gideon
y si tengo una lista de tipo complejo (persona) y quiero tener un tiempo dentro de cada persona? Necesito que esto se ejecute en el mismo hilo (todas las personas), porque si llama al método de la primera persona, la segunda persona debe esperar hasta que la primera termine el evento transcurrido. ¿Puedo hacer eso?
Leandro De Mello Fagundes
4
Todo el mundo ... System.Timers.Timertiene dos modos de funcionamiento. Puede ejecutarse en un subproceso de grupo de subprocesos asignado aleatoriamente O puede ejecutarse en cualquier subproceso que aloje la ISynchronizeInvokeinstancia. No sé cómo dejar eso más claro. System.Threading.Timertiene poco (si es que tiene algo) que ver con la pregunta original.
Brian Gideon
@LeandroDeMelloFagundes ¿No puedes usar lockpara eso?
Ozkan
24

Cada evento transcurrido se activará en el mismo hilo a menos que se esté ejecutando un evento anterior.

Entonces maneja la colisión por ti

intenta poner esto en una consola

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);
}

obtendrás algo como esto

10
6
12
6
12

donde 10 es el hilo de llamada y 6 y 12 se disparan desde el evento bg transcurrido. Si quita Thread.Sleep (2000); obtendrás algo como esto

10
6
6
6
6

Dado que no hay colisiones.

Pero esto todavía te deja con un problema. Si dispara el evento cada 5 segundos y se tarda 10 segundos en editar, necesita un poco de bloqueo para omitir algunas ediciones.

Simón
fuente
8
Agregar un timer.Stop()al comienzo del método de evento Elapsed y luego timer.Start()al final del método de evento Elapsed evitará que el evento Elapsed choque.
Metro Smurf
4
No necesitas poner timer.Stop () solo necesitas definir timer.AutoReset = false; luego crea timer.Start () después de procesar el evento. Creo que esta es una mejor forma de evitar colisiones.
João Antunes
17

Para System.Timers.Timer, en un subproceso separado, si SynchronizingObject no está configurado.

    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;
    }

Salida que vería si DummyTimer se disparara en un intervalo de 5 segundos:

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

Entonces, como se ve, OnDummyTimerFired se ejecuta en el hilo de Workers.

No, más complicaciones: si reduce el intervalo para decir 10 ms,

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

Esto se debe a que si la ejecución previa de OnDummyTimerFired no se realiza cuando se activa el siguiente tick, entonces .NET crearía un nuevo hilo para hacer este trabajo.

Para complicar aún más las cosas, "La clase System.Timers.Timer proporciona una manera fácil de lidiar con este dilema: expone una propiedad pública SynchronizingObject. Establecer esta propiedad en una instancia de un formulario de Windows (o un control en un formulario de Windows) asegurará que el código de su controlador de eventos Elapsed se ejecuta en el mismo hilo en el que se creó una instancia de SynchronizingObject ".

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

Swab.Jat
fuente
Ese es un buen ejemplo. No lo sabía hasta ahora y necesito poner algunas cerraduras aquí y allá. Perfecto.
Olaru Mircea
¿Y si quiero que el temporizador active el evento Elapsed en el hilo principal de una aplicación de consola?
jacktric
13

Si el evento transcurrido tarda más que el intervalo, se creará otro hilo para generar el evento transcurrido. Pero hay una solución para esto

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Rajan
fuente
+1 Respuesta simple y limpia, pero esto no siempre funcionará. En su lugar, se debe usar la propiedad lock o SynchronizingObject del temporizador.
RollerCosta