2010-04-02 3 views
14

Ich hatte eine Situation, die nach einer Verzögerung einen Lambda-Ausdruck auf dem UI-Thread ausführen musste. Ich dachte an mehrere Möglichkeiten, dies zu tun und schließlich.NET: Der beste Weg, um ein Lambda im UI-Thread nach einer Verzögerung auszuführen?

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

auf diesem Ansatz angesiedelt Aber ich frage mich, ob es einen einfacheren Weg, die ich verpasst. Irgendwelche Vorschläge für eine kürzere, einfachere oder einfachere Technik? Angenommen, .NET 4 ist verfügbar.

+1

Wenn Sie mit WPF verwenden [DispatcherTimer] (http: // msdn .microsoft.com/de-us/library/system.windows.threading.dispatchertimer.aspx) –

Antwort

22

Ich denke, was Sie haben, ist ziemlich gut Scott.

Das einzige kleine Problem, das einige damit haben könnten, ist, dass Sie einen Thread blockieren, um Ihre Verzögerung auszuführen. Natürlich ist es ein Hintergrund-Thread und es ist unwahrscheinlich, dass es Probleme verursacht, es sei denn, Sie führen viele dieser Aufrufe gleichzeitig aus (jeder bindet einen Thread), aber es ist immer noch wahrscheinlich suboptimal.

Ich würde stattdessen vorschlagen, dass Sie den Algorithmus in eine Hilfsmethode Faktor und vermeiden, Thread.Sleep zu verwenden.

Es gibt offensichtlich wohl unzählige Möglichkeiten, dies zu tun, aber man hier:

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

zu benutzen:

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

Natürlich könnte man auch eine Implementierung dieses DelayExecution Methode schreiben, die andere Arten verwendet B. der WPF DispatcherTimer oder die WinForms Timer-Klasse. Ich bin nicht sicher, was die Kompromisse dieser verschiedenen Timer sein würden. Meine Vermutung wäre, dass die Timer von DispatcherTimer und WinForm tatsächlich auf Anwendungen des entgegengesetzten Typs funktionieren würden.

EDIT:

Re-Lektüre meine Antwort, ich glaube eigentlich würde ich dies in eine Erweiterung Methode versucht sein, Faktor, die auf Synchronisationskontexten funktioniert - wenn man darüber nachdenkt, eine allgemeinere Aussage wäre Sie müssen in der Lage sein, die Arbeit nach einer bestimmten Verzögerung in einen Synchronisationskontext zurückzuschreiben.

Der SynchronizationContext verfügt bereits über eine Post-Methode für Warteschlangen, die der ursprüngliche Aufrufer nach Abschluss nicht blockieren möchte.Was wir brauchen, ist eine Version davon, dass die Arbeit nach einer Verzögerung postet, so statt:

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

und Verwendung:

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

Ich mag Ihre Erweiterung Methode Ansatz! Es ist eine saubere Syntax und vermeidet den Aufruf von sleep(). Der einzige Nachteil ist, dass dem Projekt eine statische Erweiterungsklasse hinzugefügt werden muss, aber das ist keine große Sache. Vielen Dank! –

+0

Arbeitet nahtlos! – AVEbrahimi

+0

Ist es möglich, die Aktion abzubrechen, bevor die verzögerte Aktion ausgeführt wird? – Kiquenet

2

Ich denke, der einfachste Weg ist die Verwendung von System.Windows.Forms.Timer, wenn Lambda nicht irgendeine zufällige Funktion ist.

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

Wenn labda nicht in der Schleife ausgeführt werden muss, fügen Sie dieses hinzu;

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

Und rufen

this.timer1.Start(); 

, wo es benötigt wird.

Eine andere Möglichkeit ist die Verwendung von Invoke-Methoden.

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 garantiert, dass Delegierten in dem UI-Thread ausgeführt werden würde

(wobei übergeordnete Fenster Haupt Deskriptor existiert) Vielleicht die bessere Variante existiert.

Verwandte Themen