2010-04-02 8 views
14

Tuve una situación que requería ejecutar una expresión lambda en el hilo de interfaz de usuario después de un retraso. Pensé en varias maneras de hacer esto y finalmente se establecieron en este enfoque.NET: ¿la mejor forma de ejecutar una lambda en el hilo de UI después de un retraso?

Task.Factory.StartNew(() => Thread.Sleep(1000)) 
    .ContinueWith((t) => textBlock.Text="Done",TaskScheduler.FromCurrentSynchronizationContext()); 

Pero me pregunto si hay una manera más fácil que echaba de menos. ¿Alguna sugerencia para una técnica más corta, más simple o más fácil? Supongamos que .NET 4 está disponible.

+1

Si está utilizando usar WPF [DispatcherTimer] (http: // MSDN .microsoft.com/es-us/library/system.windows.threading.dispatchertimer.aspx) –

Respuesta

22

Creo que lo que tienes es bastante bueno Scott.

El único problema leve que creo que algunos podrían tener con él, es que está bloqueando un hilo para ejecutar su retraso. Por supuesto, se trata de un hilo de fondo, y es poco probable que cause problemas a menos que ejecutes muchas de estas llamadas al mismo tiempo (cada una atando un hilo), pero probablemente aún no sea óptimo.

En su lugar, le sugiero que factorice el algoritmo en un método de utilidad, y evite el uso de Thread.Sleep.

Obviamente hay probablemente innumerables maneras de hacer esto, pero aquí está uno:

public static class UICallbackTimer 
{ 
    public static void DelayExecution(TimeSpan delay, Action action) 
    { 
     System.Threading.Timer timer = null; 
     SynchronizationContext context = SynchronizationContext.Current; 

     timer = new System.Threading.Timer(
      (ignore) => 
      { 
       timer.Dispose(); 

       context.Post(ignore2 => action(), null); 
      }, null, delay, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

de usar:

UICallbackTimer.DelayExecution(TimeSpan.FromSeconds(1), 
     () => textBlock.Text="Done"); 

Por supuesto, también podría escribir una implementación de este método DelayExecution que utiliza otros tipos de temporizador como el WPF DispatcherTimer o la clase WinForms Timer. No estoy seguro de cuáles serían las compensaciones de estos varios temporizadores. Creo que los temporizadores de DispatcherTimer y WinForm seguirían funcionando en aplicaciones del tipo opuesto.

EDIT:

Releyendo mi respuesta, creo que en realidad estaría tentado a este factor en un método de extensión que trabaja en contextos de sincronización - si se piensa en ello, una declaración más general sería que necesita poder volver a publicar el trabajo en un contexto de sincronización después de un cierto retraso.

El SynchronizationContext ya tiene un método de publicación para el trabajo en cola, que el llamador original no desea bloquear al finalizar.Lo que necesitamos es una versión de este que los puestos de trabajo después de un retraso, por lo que en su lugar:

public static class SyncContextExtensions 
{ 
    public static void Post(this SynchronizationContext context, TimeSpan delay, Action action) 
    { 
     System.Threading.Timer timer = null; 

     timer = new System.Threading.Timer(
      (ignore) => 
      { 
       timer.Dispose(); 

       context.Post(ignore2 => action(), null); 
      }, null, delay, TimeSpan.FromMilliseconds(-1)); 
    } 
} 

y uso:

 SynchronizationContext.Current.Post(TimeSpan.FromSeconds(1), 
      () => textBlock.Text="Done"); 
+0

¡Me gusta su enfoque de método de extensión! Es una sintaxis limpia y evita la llamada sleep(). La única desventaja es la necesidad de agregar una clase de extensión estática al proyecto, pero eso no es gran cosa. ¡Gracias! –

+0

¡Funciona sin problemas! – AVEbrahimi

+0

¿Es posible cancelar la acción antes de ejecutar la acción retrasada? – Kiquenet

2

Creo que la manera más simple es usar System.Windows.Forms.Timer, si lambda no es una función aleatoria.

this._timer.Interval = 1000; 
this._timer.Tick += (s, e) => this.textBlock.Text = "Done"; 

Si no es necesario ejecutar labda en el ciclo, agréguenlo;

this.timer1.Tick += (s, e) => this.timer1.Stop(); 

Y llama

this.timer1.Start(); 

donde se necesita.

Otra forma es utilizar Invoke methodes.

delegate void FooHandler(); 

private void button1_Click(object sender, EventArgs e) 
     { 

      FooHandler handle =() => Thread.Sleep(1000); 
      handle.BeginInvoke(result => { ((FooHandler)((AsyncResult)result).AsyncDelegate).EndInvoke(result); this.textBox1.Invoke((FooHandler)(() => this.textBox1.Text = "Done")); }, null); 
     } 

Control.Invoke garantiza que el delegado se ejecutaría en el hilo de interfaz de usuario (donde existe ventana padre descriptor principal)

existe Tal vez la mejor variante.

Cuestiones relacionadas