Цепочка задач без TaskCompletionSource?


Я преобразую некоторый код async / await в цепочку задач, поэтому я могу использовать его в выпущенном фреймворке. Код ожидания выглядит следующим образом

public async Task<TraumMessage> Get() {
  var message = await Invoke("GET");
  var memorized = await message.Memorize();
  return memorized;
}

Где

Task<TraumMessage> Invoke(string verb) {}
Task<TraumMessage> Memorize() {}

Я надеялся связать Invoke и Memorize, чтобы вернуть задачу, созданную Memorize, но это приводит к Task<Task<TraumMessage>. Решение, которое я получил, - это TaskCompletionSource<TraumMessage> в качестве моего сигнала:

public Task<TraumMessage> Get() {
  var completion = new TaskCompletionSource<TraumMessage>();
  Invoke("GET").ContinueWith( t1 => {
     if(t1.IsFaulted) {
       completion.SetException(t1.Exception);
       return;
     }
     t1.Result.Memorize().ContinueWith( t2 => {
       if(t2.IsFaulted) {
         completion.SetException(t2.Exception);
         return;
       }
       completion.SetResult(t2.Result);
     });
  });
  return completion.Task;
}
Есть ли способ сделать это без TaskCompletionSource?
3 5

3 ответа:

Я думаю, что это практически единственный способ добиться того, чего вы хотите. Связывание разрозненных задач вместе не поддерживается API продолжения, поэтому вы должны прибегнуть к использованию TaskCompletionSource, как будто вы должны координировать работу.

У меня нет асинхронного CTP, установленного на этой машине, но почему бы вам не взглянуть на код с помощью декомпилятора (или ILDASM, если вы знаете, как читать IL), чтобы увидеть, что он делает. Держу пари, он делает что-то очень похожее на ваш код TCS под одеялом.

Да, фреймворк поставляется с удобным методом расширения Unwrap () именно для того, что вы хотите.

Invoke("GET").ContinueWith( t => t.Result.Memorize() ).Unwrap();

Если вы делаете отмену, то Вам, очевидно, нужно будет передать жетоны отмены в соответствующие места.

Можно использовать вложенные дочерние задачи. Родительская задача перейдет в состояние завершено только после завершения всех дочерних задач. Исключения распространяются на родительскую задачу. Вам понадобится держатель результата, так как результат будет назначен после делегата родительской задачи, но будет установлен, когда будут запущены продолжения родительских задач.

Вот так:

public class Holder<T> where T: class
{
   public T Value { get; set; }
}

public Task<Holder<TraumMessage>> Get() {
  var invokeTask = Invoke("GET");
  var result = invokeTask.ContinueWith<Holder<TraumMessage>>(t1 => {
    var holder = new Holder<TraumMessage>();
    var memorizeTask = t1.Result.Memorize();
    memorizeTask.ContinueWith(t2 => {
      holder.Value = t2.Result;
    }, TaskContinuationOptions.AttachedToParent);
    return holder;
  });
  return result;
}