2012-05-21 12 views
6

Wszystko, tutaj jest pytanie o projekt/najlepsze praktyki złożonego przypadku anulowania Zadanie: s w języku C#. Jak wdrażasz anulowanie udostępnionego zadania?Jak zaimplementować anulowanie współdzielonego zadania: s w C#

Jako minimalny przykład przyjmijmy następujące; mamy długotrwałą, wspólnie odwoływaną operację "Praca". Akceptuje token odwołania jako argument i wyrzuca, jeśli został anulowany. Działa na pewnym stanie aplikacji i zwraca wartość. Jego wynik jest niezależnie wymagany przez dwa składniki interfejsu użytkownika.

Podczas gdy stan aplikacji pozostaje niezmieniony, wartość funkcji Praca powinna być buforowana, a jeśli jedno obliczenie jest w toku, nowe żądanie nie powinno rozpoczynać drugiego obliczenia, lecz raczej czekać na wynik.

Każdy z komponentów interfejsu użytkownika powinien mieć możliwość anulowania jego zadania bez wpływania na inne zadania komponentów interfejsu użytkownika.

Jesteś ze mną do tej pory?

Powyższe można osiągnąć, wprowadzając pamięć podręczną zadań, która opakowuje rzeczywiste zadanie robocze w TaskCompletionSources, którego zadania: s są następnie zwracane do składników interfejsu użytkownika. Jeśli składnik interfejsu użytkownika anuluje zadanie, porzuci tylko zadanie TaskCompletionSource, a nie podstawowe zadanie. To wszystko jest dobre. Składniki interfejsu użytkownika tworzą źródło anulowania, a żądanie anulowania to normalny projekt z góry do dołu, a współdziałające zadanie TaskCompletionSource znajduje się na dole.

Teraz, na prawdziwy problem. Co zrobić, gdy zmienia się stan aplikacji? Załóżmy, że posiadanie funkcji "Praca" działa na kopii stanu, jest niewykonalne.

Jednym z rozwiązań byłoby słuchanie zmiany stanu w pamięci podręcznej zadań (lub około). Jeśli pamięć podręczna ma Token Anulowania używany przez zadanie podstawowe, ten, który uruchamia funkcję Praca, może go anulować. Mogłoby to następnie spowodować anulowanie wszystkich dołączonych zadań TaskCompletionSources: s, a zatem oba składniki interfejsu użytkownika otrzymałyby anulowane zadania. Jest to rodzaj anulowania oddolnego.

Czy jest to preferowany sposób na zrobienie tego? Czy istnieje wzorzec projektowy, który opisuje je w niektórych miejscach?

Odwołanie od dołu można zastosować, ale wydaje się nieco dziwne. Zadanie UI jest tworzone z Tokenem Anulowania, ale jest anulowane z powodu innego (wewnętrznego) Tonu Anulowania. Ponadto, ponieważ tokeny nie są takie same, wyjątek OperationCancelledException nie może być po prostu ignorowany w interfejsie użytkownika, co może doprowadzić do sytuacji, w której wyjątek zostanie zgłoszony w zewnętrznym finalizatorze zadań.

+0

Doskonałe pytanie, chociaż moja pierwsza reakcja była "tl; dr" – dtb

+1

Tak więc, aby wyjaśnić; masz dwóch konsumentów wyniku zadania, a chcesz tylko jedno zadanie, aby obliczyć wartość na stan aplikacji; mimo to chcesz, aby każdy konsument mógł "anulować" oczekiwanie na wynik zadania? – Tejs

+0

tak, ale także, że zmiana stanu powinna anulować obliczenia. – 4ZM

Odpowiedz

1

Brzmi jak chcesz chciwy zestaw operacji zadanie - masz dostawcę wynik zadanie, a następnie skonstruować zadanie ustawić, aby powrócić pierwszej zakończonej operacji Ex:

// Task Provider - basically, construct your first call as appropriate, and then 
// invoke this on state change 

public void OnStateChanged() 
{ 
    if(_cts != null) 
     _cts.Cancel(); 

    _cts = new CancellationTokenSource(); 
    _task = Task.Factory.StartNew(() => 
     { 
      // Do Computation, checking for cts.IsCancellationRequested, etc 
      return result; 
     }); 
} 

// Consumer 1 

var cts = new CancellationTokenSource(); 
var task = Task.Factory.StartNew(() => 
    { 
     var waitForResultTask = Task.Factory.StartNew(() => 
      { 
       // Internally, this is invoking the task and waiting for it's value 
       return MyApplicationState.GetComputedValue(); 
      }); 

     // Note this task cares about being cancelled, not the one above 
     var cancelWaitTask = Task.Factory.StartNew(() => 
     { 
       while(!cts.IsCancellationRequested) 
       Thread.Sleep(25); 

       return someDummyValue; 
     }); 

     Task.WaitAny(waitForResultTask, cancelWaitTask); 

     if(cancelWaitTask.IsComplete) 
      return "Blah"; // I cancelled waiting on the original task, even though it is still waiting for it's response 
     else 
      return waitForResultTask.Result; 
    }); 

Teraz haven” w pełni to przetestowało, ale powinno pozwolić ci na "anulowanie" czekania na zadanie przez anulowanie tokena (i przez to zmuszenie zadania "czekać" do ukończenia najpierw i uderzenie w WaitAny) i pozwolić ci "anulować" zadanie obliczeniowe .

Inną sprawą jest wymyślić czysty sposób wykonania zadania "anuluj" bez strasznego blokowania. Myślę, że to dobry początek.

+0

Użyłem klasyTaskCompletionSource, aby zrobić to, co robi twój konsument. Ale problem jest taki sam. Po wywołaniu OnStateChange i anulowaniu "prawdziwego" zadania funkcja MyApplicationState.GetComputedValue() rzuci wyjątek OpCanceledException. Zostanie on ponownie zgłoszony przez Task.WaitAny, a następnie "zadanie" będzie wymagać specjalnej troski, aby nie wysadzić w finalizatorze. – 4ZM

+1

To tylko jeśli użyjesz 'cts.ThrowIfCancellationIsRequested' - jeśli po prostu sprawdzisz warunek w wątku, żaden wyjątek nie zostanie zgłoszony, jeśli po prostu zwrócisz fałszywą wartość. Sam sprawdzasz token, zamiast ramek, które to robią. Alternatywnie, możesz po prostu owinąć 'Task.WaitAny' z wewnętrzną próbą/catch i obsłużyć wyjątek przez zwrot zwracanej wartości" anulowanej "(lub cokolwiek jest właściwe). – Tejs

+0

Masz rację. Używanie specjalnej wartości zwracanej do anulowania (zamiast używania wyjątków) jest jednym ze sposobów implementacji tego, co chcę. Musiałbym wprowadzić wartość atrapową (lub nowy typ) i wydaje się, że jest to sprzeczne z ogólnym zastosowaniem anulowania obsługi z wyjątkami w ramach .Net Tasks. Trzymam kciuki za lepsze rozwiązanie, ale to zadziała. Dzięki. – 4ZM

1

Oto moja próba:

// the Task for the current application state 
Task<Result> _task; 
// a CancellationTokenSource for the current application state 
CancellationTokenSource _cts; 

// called when the application state changes 
void OnStateChange() 
{ 
    // cancel the Task for the old application state 
    if (_cts != null) 
    { 
     _cts.Cancel(); 
    } 

    // new CancellationTokenSource for the new application state 
    _cts = new CancellationTokenSource(); 
    // start the Task for the new application state 
    _task = Task.Factory.StartNew<Result>(() => { ... }, _cts.Token); 
} 

// called by UI component 
Task<Result> ComputeResultAsync(CancellationToken cancellationToken) 
{ 
    var task = _task; 
    if (cancellationToken.CanBeCanceled && !task.IsCompleted) 
    { 
     task = WrapTaskForCancellation(cancellationToken, task); 
    } 
    return task; 
} 

z

static Task<T> WrapTaskForCancellation<T>(
    CancellationToken cancellationToken, Task<T> task) 
{ 
    var tcs = new TaskCompletionSource<T>(); 
    if (cancellationToken.IsCancellationRequested) 
    { 
     tcs.TrySetCanceled(); 
    } 
    else 
    { 
     cancellationToken.Register(() => 
     { 
      tcs.TrySetCanceled(); 
     }); 
     task.ContinueWith(antecedent => 
     { 
      if (antecedent.IsFaulted) 
      { 
       tcs.TrySetException(antecedent.Exception.GetBaseException()); 
      } 
      else if (antecedent.IsCanceled) 
      { 
       tcs.TrySetCanceled(); 
      } 
      else 
      { 
       tcs.TrySetResult(antecedent.Result); 
      } 
     }, TaskContinuationOptions.ExecuteSynchronously); 
    } 
    return tcs.Task; 
} 
+0

To jest coś, co zrobiłem. Działa, ale czy to dobry projekt? Z perspektywy UI zadanie jest anulowane przez kogoś innego. Dzięki temu rozwiązaniu interfejs użytkownika będzie musiał również jawnie obsługiwać wyjątki anulowania z zadań wewnętrznych, jeśli tworzy dodatkowe zadania związane z opakowaniem, aby uniknąć problemu "wyjątek nieoczekiwany został powtórnie zgłoszony przez wątek finalizatora". – 4ZM

Powiązane problemy