2015-02-04 9 views
9

Piszę bibliotekę, która zawiera funkcję planowania (nie standardową TaskScheduler, IScheduler ...) opartą na .Net Tasks. Używam TaskCompletionSource, a Task.Status ma kluczowe znaczenie dla reprezentowania statusu operacji podstawowych, w tym , w tymTaskStatus.Created, tj. Utworzono, ale jeszcze się nie rozpoczęto. Wiem, że zwrócone zadania powinny normalnie być gorące, ale dla moich ręcznie sterowanych zadań proxy, naprawdę chcę ich początkowo jako Created.Utwórz lodowaty TaskCompletionSource?

Niestety dla mnie początkowy status TaskCompletionSource.Task to WaitingForActivation, czyli już minął Created. Mówiąc inaczej, TaskCompletionSource obsługuje dwa stany, ale muszę trzy stany:

Pytanie: Jak mogę dostać Task że mogę ręcznie ustawić trzech różnych państw? To znaczy. Task.Status może być ustawiony do:

1) Created
2) Jedną WaitingForActivation/WaitingForChildrenToComplete/WaitingToRun/Running
3) albo z RanToCompletion/Canceled/Faulted

Kod poniżej zrozumiałe, skarży się na niezgodność typów . Zamiast tego mogę zamienić zadanie, zmieniając new Task<TResult> na new Task<Task<TResult>>, ale aby wrócić do Task<TResult> muszę je ustawić na Unwrap(), a niezapakowane zadanie będzie miało status WaitingForActivation, przywracając mnie z powrotem do kwadratu.

Będę miał ich dużą liczbę, więc zablokowanie wątku z Wait() dla każdego nie jest opcją.

Rozważałam dziedziczenie z Task i nadrzędne członków (stosując nowy), ale jeśli to możliwe, że byłoby miło, aby dać użytkownikowi Biblioteka rzeczywiste Task zamiast DerivedTask, zwłaszcza odkąd przedstawiania okresowych zadań należy również oczekiwane w wielu inne miejsca.

Pomysły?

private TaskCompletionSource<TResult> tcs; 

private async Task<TResult> CreateStartCompleteAsync() 
{ 
    await tcs.Task; 
    if (tcs.Task.IsCanceled) 
    { 
     throw new OperationCanceledException(""); 
    } 
    else if // etc. 
} 

public ColdTaskCompletionSource() 
{ 
    tcs = new TaskCompletionSource<TResult>(); 
    Task = new Task<TResult>(() => CreateStartCompleteAsync()); 
} 

Błędy:
* Nie można przekonwertować wyrażenia lambda delegować typu „System.Func”, ponieważ niektóre rodzaje zwrotu w bloku nie są niejawnie zamienny do zwracanego typu delegat
* Nie można niejawnie przekonwertować typu ' System.Threading.Tasks.Task 'to' TResult '

+0

Myślę, że odpowiedziałem na ściśle powiązane pytanie [tutaj] (http://stackoverflow.com/a/22705236/1768303). – Noseratio

+2

Myślę, że jeśli chcesz mieć większą kontrolę, nie powinieneś oprzeć swojego API na 'Zadanie ', ale na niestandardowej klasie, która eksponuje dokładnie te informacje, które chcesz eksponować. Możesz również wystawić "zadanie", ale informacja o statusie pochodzi od twojej klasy. – usr

+0

@Noseratio: "Zadanie" w połączonym kodzie przechodzi tylko przez 2 stany: "Utworzone" i "Ukończone", podobnie do "TaskCompletionSource.Task" przechodzące tylko przez 2 stany ('WaitingForActivation' i" Completed "). Potrzebuję ** 3 ** stany ('Created',' Running', "Completed"), a mój przykładowy kod powyżej próbuje użyć async-await, aby to osiągnąć, ale jak wyjaśniono nie działa z powodu niedopasowania typu. Nie widzę sposobu modyfikowania połączonego kodu w celu obsługi stanów ** 3 ** - jakichkolwiek pomysłów? –

Odpowiedz

6

Chociaż zgadzam się z punktów @ usr w komentarzach, technicznie nadal można mieć implementację, która zapewnia te stany:

  1. Created
  2. WaitingToRun
  3. Albo RanToCompletion/Canceled/Faulted

Aby unikaj blokowania wątków za pomocą Task.Wait, możesz użyć wewnętrznego pomocnika TaskScheduler, który najpierw przeniesie zadanie z Created na WaitingToRun i ostatecznie do jednego z ukończonych stanów.

Poniższy kod ilustruje tę koncepcję. Został on tylko nieznacznie przetestowany, może nie być całkowicie bezpieczny w wątkach i być może mógłby zostać ulepszony w celu współużytkowania pojedynczego wystąpienia FakeTaskScheduler w wielu zadaniach.

public class ColdTaskCompletionSource 
{ 
    public sealed class FakeTaskScheduler : TaskScheduler 
    { 
     Task _task; 

     public FakeTaskScheduler() 
     { 
     } 

     protected override void QueueTask(Task task) 
     { 
      _task = task; 
     } 

     protected sealed override bool TryDequeue(Task task) 
     { 
      if (task != _task) 
       return false; 

      _task = null; 
      return true; 
     } 

     protected override IEnumerable<Task> GetScheduledTasks() 
     { 
      if (_task == null) 
       yield break; 
      yield return _task; 
     } 

     protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) 
     { 
      return false; 
     } 

     public override int MaximumConcurrencyLevel 
     { 
      get { return 1; } 
     } 

     public bool Execute() 
     { 
      if (_task == null) 
       return false; 

      var task = _task; 
      _task = null; 
      return base.TryExecuteTask(task); 
     } 
    } 

    readonly Task _task; 
    readonly CancellationTokenSource _cts; 
    readonly object _lock = new Object(); 
    readonly FakeTaskScheduler _ts = new FakeTaskScheduler(); 
    Action _completionAction = null; 

    // helpers 

    void InvokeCompletionAction() 
    { 
     if (_completionAction != null) 
      _completionAction(); 
    } 

    void Complete() 
    { 
     if (_task.Status != TaskStatus.WaitingToRun) 
      throw new InvalidOperationException("Invalid Task state"); 
     _ts.Execute(); 
    } 

    // public API 

    public ColdTaskCompletionSource() 
    { 
     _cts = new CancellationTokenSource(); 
     _task = new Task(InvokeCompletionAction, _cts.Token); 
    } 

    public Task Task { get { return _task; } } 

    public void Start() 
    { 
     _task.Start(_ts); 
    } 

    public void SetCompleted() 
    { 
     lock (_lock) 
      Complete(); 
    } 

    public void SetException(Exception ex) 
    { 
     lock (_lock) 
     { 
      _completionAction =() => { throw ex; }; 
      Complete(); 
     } 
    } 

    public void SetCancelled() 
    { 
     lock (_lock) 
     { 
      _completionAction =() => 
      { 
       _cts.Cancel(); 
       _cts.Token.ThrowIfCancellationRequested(); 
      }; 
      Complete(); 
     } 
    } 
} 
+4

Pomysłowy hack. – usr

+1

@Noseratio: To działało bardzo dobrze, bardzo doceniane! Powyższy kod _requires_ wywołanie Start() zgodnie z moją "specyfikacją". Poprawiłem go, aby wywoływać Start() opcjonalnie, biorąc blokadę również w Start(): 'lock (_lock) _task.Start (_ts);', i w Complete() zamiast rzucania robię: 'if (_task. Status == TaskStatus.Created) _task.Start (_ts); ' –

2

Nie możesz w rozsądny sposób.

Status Task jest obsługiwany wewnętrznie, a jedynym API do ręcznego tworzenia zadań jest użycie TaskCompletionSource. Nie można również dziedziczyć po Task, ponieważ właściwość Status jest tylko modułem pobierającym i nie można uzyskać dostępu do pola kopii zapasowej m_stateFlags, ponieważ jest wewnętrzna.

Jednak nierozsądny sposób byłoby utworzyć zadanie, które czeka na TaskCompletionSource i kontrolować stan zadaniem jest być kontrolowanie TaskCompletionSource:

var taskCompletionSource = new TaskCompletionSource<bool>(); 
var cancellationTokenSource = new CancellationTokenSource(); 
var task = new Task(() => taskCompletionSource.Task.Wait(cancellationTokenSource.Token), cancellationTokenSource.Token); // task.Status == TaskStatus.Created 

task.Start(); // task.Status == TaskStatus.Running 

taskCompletionSource.SetResult(false) // task.Status == TaskStatus.RanToCompletion 

Albo

taskCompletionSource.SetException(new Exception("")) // task.Status == TaskStatus.Faulted 

Albo

cancellationTokenSource.Cancel() // task.Status == TaskStatus.Cancelled 

Naprawdę nie polecam tego. Nie jestem pewien, dlaczego chcesz kontrolować status Task, ale prawdopodobnie musisz stworzyć własną konstrukcję, którą możesz zarządzać, zamiast wymuszać na Task projekt, do którego nie był przeznaczony.

+0

Po co tworzyć "działające" uruchamianie na gorąco tylko po to, aby zablokować wątek puli za pomocą 'Task.Wait()'? To całkiem [możliwe] (http://stackoverflow.com/a/22705236) do symulacji 'TaskCompletionSource' z zimnym' Task'. To może nie być doskonałe, ponieważ eksponuje wewnętrzne zadanie dla rozmówcy, ale IMO jest jeszcze lepsze niż blokowanie. – Noseratio

+0

@Noseratio * "Jak mogę uzyskać zadanie, w którym ręcznie ustawiam status (utworzony, ** działający **, ranToCompletion, anulowany, zaatakowany)" * – i3arnon

+0

@ i3arnon: Edytowano pytanie - mam wiele z nich i 'Zaczekaj() 'nie jest opcją. –