.NET: ¿La mejor manera de ejecutar un lambda en el hilo de la interfaz de usuario después de un retraso?

Tuve una situación que requería la ejecución de una expresión lambda en el subproceso de la interfaz de usuario después de un retraso. Pensé en varias formas de hacer esto y finalmente me decidí por este enfoque.

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

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

Creo que lo que tienes es bastante bueno Scott.

El único problema leve que creo que algunos podrían tener, es que estás bloqueando un hilo para ejecutar tu demora. Por supuesto, es un subproceso en segundo plano, y es poco probable que cause problemas a menos que ejecute muchas de estas llamadas al mismo tiempo (cada una está atando un subproceso), pero aún así es probablemente subóptimo.

En su lugar, sugeriría que factorices el algoritmo en un método de utilidad y evites usar Thread.Sleep.

Obviamente hay probablemente innumerables formas de hacer esto, pero aquí hay una:

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

Usar:

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

Por supuesto, también puede escribir una implementación de este método DelayExecution que use otros tipos de temporizadores como el DispatcherTimer de WPF o la clase de temporizador de WinForms. No estoy seguro de cuáles serían las ventajas y desventajas de estos distintos temporizadores. Mi conjetura sería que los temporizadores de DispatcherTimer y WinForm aún funcionarían en aplicaciones del tipo opuesto.

EDITAR:

Al volver a leer mi respuesta, creo que en realidad me sentiría tentado a tener esto en cuenta en un método de extensión que funciona en contextos de sincronización. Si lo piensa, una afirmación más general sería que debe poder publicar el trabajo en una contexto de sincronización después de un cierto retraso.

SynchronizationContext ya tiene un método de publicación para el trabajo en cola, que el llamante original no desea bloquear al finalizar. Lo que necesitamos es una versión de esto que publique el trabajo después de un retraso, así 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 use:

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

Creo que la forma más sencilla 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 labda no necesita ejecutarse en el bucle, agregue esto;

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

Y llama

 this.timer1.Start(); 

donde se necesite

Otra forma es usar métodos Invoke.

 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á en el subproceso de la interfaz de usuario (donde existe el descriptor principal de la ventana principal)

Tal vez exista la mejor variante.