2008-12-01 17 views
6

Digamos que tengo una instancia System.Threading.Timer existente y que me gustaría llamar Cambio en él para empujarlo está disparando tiempo atrás:"atómicamente" cambiar un System.Threading.Timer

var timer = new Timer(DelayCallback, null, 10000, Timeout.Infinite); 
// ... (sometime later but before DelayCallback has executed) 
timer.Change(20000, Timeout.Infinite); 

I Estoy usando este temporizador para realizar una "devolución de llamada inactiva" después de un período sin actividad. ("Inactivo" y "sin actividad" son condiciones definidas por la aplicación en este caso ... los detalles no son terriblemente importantes.) Cada vez que realizo una "acción", quiero reiniciar el temporizador para que siempre esté configurado para disparar 10 segundos después de eso.

Sin embargo, hay una condición de carrera inherente porque cuando llamo a Change, no puedo decir si el temporizador ya se ha disparado en función de su configuración anterior. (Puedo, por supuesto, decir si mi devolución de llamada ha sucedido, pero no puedo decir si el hilo del temporizador interno de CLR ha puesto en cola mi devolución de llamada al hilo y su ejecución es inminente.)

Ahora sé que puedo llamar Eliminar en la instancia del temporizador y volver a crearlo cada vez que necesito "empujarlo hacia atrás". pero este parece menos eficaz que simplemente cambiar el temporizador existente. Por supuesto, puede no ser ... Voy a ejecutar algunos micro-puntos de referencia en un momento y que todos ustedes lo sepan.

Como alternativa, siempre puedo realizar un seguimiento del tiempo de activación esperado (mediante DateTime.Now.AddSeconds (10)) y, si el temporizador original se dispara, ignórelo marcando DateTime.Now en la devolución de llamada. (Tengo una inquietud persistente de que esto puede no ser 100% confiable debido al temporizador usando TimeSpan y mi cheque usando DateTime ... esto puede no ser un problema pero no me siento completamente cómodo con él por alguna razón ...

)

Mis preguntas son:

  1. ¿hay una buena manera para que llame a Timer.Change y ser capaz de saber si me las arreglé para cambiarlo antes de la devolución de llamada se puso en cola a la threadpool? (No lo creo, pero no está de más preguntar ...)
  2. ¿Alguien más ha implementado (lo que yo denomino) un "temporizador de retroceso" como este? Si es así, me encantaría saber cómo resolvió el problema.

Esta pregunta es de naturaleza hipotética ya que ya tengo un par de soluciones de trabajo (basadas en Dispose y basadas en DateTime.Now) ... Estoy interesado principalmente en escuchar sugerencias relacionadas con el rendimiento (como yo "presionaré" el temporizador MUY frecuentemente).

Gracias!

Respuesta

0

De hecho, tuve que crear mi propia clase "Timing" para un MMORPG que hice. Podría realizar un seguimiento de más de 100.000 "entidades" que tenían temporizadores para procesar AI y otras tareas. En función de las diferentes acciones que podrían tomarse, tendría que retrasar momentáneamente un evento.

Ahora, mi clase de cronometraje fue escrita completamente a mano, por lo que no será exactamente lo que está buscando. Sin embargo, algo que se podía hacer eso sería similar a la solución que se me ocurrió es hacer una especie de:

while (sleepyTime > 0) 
{ 
    int temp = sleepyTime; 
    sleepyTime = 0; 
    Thread.Sleep(temp); 
} 

// here's where your actual code is. 

A continuación, puede hacer que un método "Delay" que, básicamente, sólo anuncios a SLEEPYTIME.

1

suena como lo que realmente quiere es la aplicación de inactividad caso

System.Windows.Forms.Application.Idle 
1

Im interpretar sus preguntas como una solicitud para una implementatation de la interfaz IdleNotifier se especifica a continuación. También indica que ActionOccured() debe ser rápido.

public delegate void IdleCallback(); 
public interface IdleNotifier 
{ 
    // Called by threadpool when more than IdleTimeSpanBeforeCallback 
    // has passed since last call on ActionOccured. 
    IdleCallback Callback { set; } 
    TimeSpan IdleTimeSpanBeforeCallback { set; } 
    void ActionOccured(); 
} 

Proporciono una implementación con System.Threading.Timer a continuación. puntos importantes acerca de la aplicación:

  • aceptamos que el temporizador puede despertar en cualquier momento y asegúrese de que esto está bien.
  • Dado que suponemos que el temporizador se activa relativamente pocas veces, podemos hacer un trabajo costoso en estos momentos.
  • Como podemos hacer toda la lógica de la devolución de llamada del temporizador, todo lo que necesitamos hacer para "presionar el temporizador" es recordar cuándo fue la última vez que lo presionamos.

Implementación:

public class IdleNotifierTimerImplementation : IdleNotifier 
{ 
    private readonly object SyncRoot = new object(); 
    private readonly Timer m_Timer; 

    private IdleCallback m_IdleCallback = null; 
    private TimeSpan m_IdleTimeSpanBeforeEvent = TimeSpan.Zero; 

    // Null means there has been no action since last idle notification. 
    private DateTime? m_LastActionTime = null; 

    public IdleNotifierTimerImplementation() 
    { 
     m_Timer = new Timer(OnTimer); 
    } 

    private void OnTimer(object unusedState) 
    { 
     lock (SyncRoot) 
     { 
      if (m_LastActionTime == null) 
      { 
       m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero); 
       return; 
      } 
      TimeSpan timeSinceLastUpdate = DateTime.UtcNow - m_LastActionTime.Value; 
      if (timeSinceLastUpdate > TimeSpan.Zero) 
      { 
       // We are no idle yet. 
       m_Timer.Change(timeSinceLastUpdate, TimeSpan.Zero); 
       return; 
      } 
      m_LastActionTime = null; 
      m_Timer.Change(m_IdleTimeSpanBeforeEvent, TimeSpan.Zero); 
     } 
     if (m_IdleCallback != null) 
     { 
      m_IdleCallback(); 
     } 
    } 

    // IdleNotifier implementation below 

    public void ActionOccured() 
    { 
     lock (SyncRoot) 
     { 
      m_LastActionTime = DateTime.UtcNow; 
     } 
    } 

    public IdleCallback Callback 
    { 
     set 
     { 
      lock (SyncRoot) 
      { 
       m_IdleCallback = value; 
      } 
     } 
    } 

    public TimeSpan IdleTimeSpanBeforeCallback 
    { 
     set 
     { 
      lock (SyncRoot) 
      { 
       m_IdleTimeSpanBeforeEvent = value; 
       // Run OnTimer immediately 
       m_Timer.Change(TimeSpan.Zero, TimeSpan.Zero); 
      } 
     } 
    } 
} 

Hay muchas mejoras en el rendimiento recta hacia adelante en el código.

Si alguien se interesara en mis primeras ideas al respecto, pregúntame.

Cuestiones relacionadas