2013-09-05 11 views
8

Chcę danej operacji wykonać przez pewien czas. Po upływie tego czasu wyślij kolejne polecenie wykonania.Jak zaimplementować Asynchronię zadań dla licznika w języku C#?

StartDoingStuff(); 
System.Threading.Thread.Sleep(200); 
StopDoingStuff(); 

Zamiast mieć oświadczenie snu, który jest tam blokowania reszty aplikacji, jak mogę napisać to za pomocą Async/zadania/czekają w C#?

+0

jestem zdezorientowany, pytasz o 'async'-'await', która to nowa funkcja C# 5.0, ale twoje pytanie jest oznaczone jako C# 4.0. Więc kto to jest? – svick

+0

przełączane znaczniki, aby dopasować pytanie – Alex

+0

@ElHaix Inni wydają się sugerować, nie rzucając wyjątku.To jest normalne, aby rzucić wyjątek, ponieważ kod będzie bardziej elegancki, obsługiwany i jest dobrym sposobem na wdrożenie *** [Zadanie Anulowanie Pattern] (http://msdn.microsoft.com/en-us/library/dd997396.aspx) *** - "użyj metody ThrowIfCancellationRequested. Zadanie anulowane w ten sposób przechodzi w stan Cancelled, czyli wywołanie kod może posłużyć do sprawdzenia, czy zadanie odpowiedziało na jego żądanie anulowania. " Wyobraź sobie, że masz wiele metod, używając tego samego tokena anulowania. Użyj wyjątku, aby uprościć całą logikę. –

Odpowiedz

10

Kwestia ta została wysłuchana Joe Hoag na blogu Parallel Team w 2011 roku: Crafting a Task.TimeoutAfter Method.

Rozwiązanie wykorzystuje TaskCompletionSource i zawiera kilka optymalizacji (12% tylko poprzez unikanie przechwytuje), uchwyty porządki i obejmuje przypadki brzegowe jak wywołanie TimeoutAfter gdy zadanie zostało już zakończone cel, przekazując nieprawidłowe limity czasu itp

Piękno z Task.Timeout Po stwierdzeniu, że bardzo łatwo jest skomponować go z innymi kontynuacjami, ponieważ robi tylko jedną rzecz: powiadamia cię, że upłynął limit czasu. Nie próbuje "anulować twojego zadania". Możesz zdecydować, co zrobić, gdy zostanie wygenerowany wyjątek TimeoutException.

Przedstawiono również szybką implementację przy użyciu Stephen Toub przy użyciu async/await, chociaż nie uwzględniono również przypadków krawędziowych.

Zoptymalizowany realizacja jest:

public static Task TimeoutAfter(this Task task, int millisecondsTimeout) 
{ 
    // Short-circuit #1: infinite timeout or task already completed 
    if (task.IsCompleted || (millisecondsTimeout == Timeout.Infinite)) 
    { 
     // Either the task has already completed or timeout will never occur. 
     // No proxy necessary. 
     return task; 
    } 

    // tcs.Task will be returned as a proxy to the caller 
    TaskCompletionSource<VoidTypeStruct> tcs = 
     new TaskCompletionSource<VoidTypeStruct>(); 

    // Short-circuit #2: zero timeout 
    if (millisecondsTimeout == 0) 
    { 
     // We've already timed out. 
     tcs.SetException(new TimeoutException()); 
     return tcs.Task; 
    } 

    // Set up a timer to complete after the specified timeout period 
    Timer timer = new Timer(state => 
    { 
     // Recover your state information 
     var myTcs = (TaskCompletionSource<VoidTypeStruct>)state; 

     // Fault our proxy with a TimeoutException 
     myTcs.TrySetException(new TimeoutException()); 
    }, tcs, millisecondsTimeout, Timeout.Infinite); 

    // Wire up the logic for what happens when source task completes 
    task.ContinueWith((antecedent, state) => 
    { 
     // Recover our state data 
     var tuple = 
      (Tuple<Timer, TaskCompletionSource<VoidTypeStruct>>)state; 

     // Cancel the Timer 
     tuple.Item1.Dispose(); 

     // Marshal results to proxy 
     MarshalTaskResults(antecedent, tuple.Item2); 
    }, 
    Tuple.Create(timer, tcs), 
    CancellationToken.None, 
    TaskContinuationOptions.ExecuteSynchronously, 
    TaskScheduler.Default); 

    return tcs.Task; 
} 

i realizacja Stephena Toub za, bez kontroli przypadków brzegowych:

public static async Task TimeoutAfter(this Task task, int millisecondsTimeout) 
{ 
    if (task == await Task.WhenAny(task, Task.Delay(millisecondsTimeout))) 
     await task; 
    else 
     throw new TimeoutException(); 
} 
2

Zakładając StartDoingStuff i StopDoingStuff zostały stworzone jako metody asynchroniczne powrocie Task następnie

await StartDoingStuff(); 
await Task.Delay(200); 
await StopDoingStuff(); 

EDIT: Jeśli oryginalna pytający chce asynchronicznej metody, które będą odwołania po upływie określonego okresu: zakładając, że metoda ta nie będzie tworzyć dowolne żądania sieciowe, ale po prostu przetwarzać je w pamięci, a wynik można dowolnie anulować bez uwzględnienia jego skutków, a następnie użyć tokenu anulowania:

private async Task Go() 
    { 
     CancellationTokenSource source = new CancellationTokenSource(); 
     source.CancelAfter(200); 
     await Task.Run(() => DoIt(source.Token)); 

    } 

    private void DoIt(CancellationToken token) 
    { 
     while (true) 
     { 
      token.ThrowIfCancellationRequested(); 
     } 
    } 

EDYCJA : Powinienem wspomnieć, że możesz złapać wynikowy wyjątek OperationCanceledException, dostarczający wskazania, w jaki sposób zadanie się zakończyło, unikając potrzeby bałaganowania z boolami.

+2

Dlaczego asynchroniczne oczekiwanie wymagałoby również dwóch metod "asynchronizacji"? – svick

+0

Poprawiłem podejście po ponownym przeczytaniu pytania. Dzięki. –

+0

To ciągłe czekanie na tokenie nie wydaje się mieć żadnego celu. – usr

2

Oto jak to zrobić, używając task cancellation pattern (opcja bez zgłaszania wyjątku).

[EDYTOR] Zaktualizowany do użycia sugestii Svick's, aby ustawić limit czasu przez CancellationTokenSourceconstructor.

// return true if the job has been done, false if cancelled 
async Task<bool> DoSomethingWithTimeoutAsync(int timeout) 
{ 
    var tokenSource = new CancellationTokenSource(timeout); 
    CancellationToken ct = tokenSource.Token; 

    var doSomethingTask = Task<bool>.Factory.StartNew(() => 
    { 
     Int64 c = 0; // count cycles 

     bool moreToDo = true; 
     while (moreToDo) 
     { 
      if (ct.IsCancellationRequested) 
       return false; 

      // Do some useful work here: counting 
      Debug.WriteLine(c++); 
      if (c > 100000) 
       moreToDo = false; // done counting 
     } 
     return true; 
    }, tokenSource.Token); 

    return await doSomethingTask; 
} 

Oto jak to nazwać z metody asynchronicznej:

private async void Form1_Load(object sender, EventArgs e) 
{ 
    bool result = await DoSomethingWithTimeoutAsync(3000); 
    MessageBox.Show("DoSomethingWithTimeout done:" + result); // false if cancelled 
} 

Oto jak nazywają go od zwykłego sposobu i obsługiwać zakończenie asynchronicznie:

private void Form1_Load(object sender, EventArgs e) 
{ 
    Task<bool> task = DoSomethingWithTimeoutAsync(3000); 
    task.ContinueWith(_ => 
    { 
     MessageBox.Show("DoSomethingWithTimeout done:" + task.Result); // false is cancelled 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 
} 
+1

Jeśli chcesz utworzyć 'AnulujToken', który jest automatycznie anulowany po pewnym czasie w .Net 4.5 istnieje przeciążenie konstruktora [a "CancellationTokenSource"] (http://msdn.microsoft.com/en-us/library/hh139115.aspx). – svick

+0

Dobry punkt @svick, zaktualizowałem kod, aby korzystać z tej funkcji. – Noseratio

+0

Powinieneś rzucić wyjątek. Z własnego linku "Wzór anulowania zadania": "Preferowanym sposobem jest użycie metody ThrowIfCancellationRequested. Zadanie anulowane w ten sposób przechodzi w stan Anulowane" –

Powiązane problemy