2010-04-02 6 views
14

Wystąpiła sytuacja wymagająca uruchomienia wyrażeń lambda w wątku interfejsu użytkownika po opóźnieniu. Myślałem o kilka sposobów, aby to zrobić i ostatecznie rozstrzygane na tym podejściu.NET: Najlepszy sposób na wykonanie lambda na wątku UI po opóźnieniu?

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

Ale zastanawiam się, czy istnieje prostszy sposób, że tęskniłem. Wszelkie sugestie dotyczące krótszej, prostszej lub łatwiejszej techniki? Załóżmy, że .NET 4 jest dostępny.

+1

Jeśli używasz WPF użyć [DispatcherTimer] (http: // msdn .microsoft.com/en-us/library/system.windows.threading.dispatchertimer.aspx) –

Odpowiedz

22

Myślę, że to, co masz, to całkiem niezły Scott.

Jedyny niewielki problem, który moim zdaniem może dotyczyć, polega na tym, że blokujesz wątek w celu wykonania opóźnienia. Oczywiście jest to wątek działający w tle i raczej nie spowoduje problemów, dopóki nie wykonasz wielu takich połączeń jednocześnie (każde z nich będzie wiązało wątek), ale nadal jest prawdopodobnie nieoptymalny.

Zamiast tego sugerowałbym, abyś uwzględnił algorytm w metodzie narzędzia i unikał używania Thread.Sleep.

Jest oczywiście zapewne niezliczone sposoby, aby to zrobić, ale tutaj jest jeden:

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

Sposób użycia:

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

Oczywiście można też napisać implementację tej metody DelayExecution który wykorzystuje inne typy timera, takiego jak WPF DispatcherTimer lub klasa WinForms Timer. Nie jestem pewien, jakie byłyby kompromisy między tymi różnymi zegarami. Domyślam się, że czasomierze DispatcherTimer i WinForm nadal działałyby w aplikacjach typu przeciwnego.

EDIT:

Ponownie czytając moją odpowiedź, myślę, że rzeczywiście chciałbym pokusić się o czynnik to pod metodę rozszerzenia, które działa od kontekstu synchronizacji - jeśli myślisz o tym, bardziej ogólne stwierdzenie byłoby że musisz mieć możliwość wysłania pracy z powrotem do kontekstu synchronizacji po pewnym opóźnieniu.

Kontekst synchronizacji ma już metodę post dla pracy w kolejce, której pierwotny rozmówca nie chce blokować po zakończeniu.To, czego potrzebujemy jest to, że wersja stanowisk pracy z opóźnieniem, więc zamiast:

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

i zastosowanie:

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

Podoba mi się Twoje podejście do metod rozszerzenia! Jest to czysta składnia i unika wywołania sleep(). Jedynym lekceważeniem jest konieczność dodania statycznej klasy rozszerzenia do projektu, ale to nie jest wielka sprawa. Dzięki! –

+0

Działa beznamiętnie! – AVEbrahimi

+0

Czy można anulować akcję przed wykonaniem opóźnionego działania? – Kiquenet

2

Myślę, że najprostszym sposobem jest użycie System.Windows.Forms.Timer, jeśli lambda nie jest funkcją losową.

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

Jeśli labda nie ma potrzeby wykonywania w pętli, dodaj to;

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

I zadzwonić

this.timer1.Start(); 

gdzie to potrzebne.

Innym sposobem jest użycie metod 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 gwarantuje, że delegat będzie wykonywany w wątku UI (gdzie istnieje okno nadrzędne główny deskryptor)

Może istnieje lepszy wariant.

Powiązane problemy