2011-07-30 9 views
5

Estoy convirtiendo un poco de código async/await en tareas encadenadas, por lo que puedo usarlo en el marco publicado. El código Await se parece a estoEncadenamiento de tareas sin TaskCompletionSource?

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

donde

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

Tenía la esperanza de cadena Invoke y Memorize volver a la tarea producida por Memorize, pero que resulta en un Task<Task<TraumMessage>. La solución que he terminado es un TaskCompletionSource<TraumMessage> como mi señal:

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

¿Hay una manera de lograr esto sin la TaskCompletionSource?

Respuesta

1

Creo que es prácticamente la única manera de lograr lo que desea. El encadenamiento de tareas dispares entre sí no es compatible con las API de continuación, por lo que debe recurrir al uso de un TaskCompletionSource como si tuviera que coordinar el trabajo.

No tengo instalado el Async CTP en esta máquina, pero ¿por qué no echas un vistazo al código con un descompilador (o ILDASM si sabes cómo leer IL) para ver qué está haciendo? Apuesto a que hace algo muy similar a tu código TCS bajo las sábanas.

+0

Gracias a la serie EduAsync de Jon Skeet, he mirado bajo el capó de async/await y básicamente construye una clase que ejecuta una máquina de estado. Ya he usado ese enfoque antes, pero es aún más tedioso que el enfoque TCS. –

+0

Ah, bueno, pensé en esto un montón más y simplemente no hay forma de continuar puro a menos que quieras bloquear la continuación de la primera tarea esperando el resultado de la continuación de la segunda tarea. Si las estrellas estuvieran alineadas, el robo de trabajo podría significar que hay muy poca sobrecarga, pero dado que teóricamente no tienes idea de cómo se creó la segunda tarea (podría ser un programador de tareas totalmente diferente o APM por ejemplo) no hay garantías para eso. Entonces debes asumir que estarías bloqueando un hilo esperando el resultado. Si quieres ver lo que quiero decir, házmelo saber y actualizaré mi respuesta. –

+1

En http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx Stephen Taub envuelve el 'TaskCompletionSource' en un método auxiliar llamado 'Then' para implementar este patrón. – Govert

0

Puede usar tareas secundarias adjuntas. La tarea principal solo realizará la transición al estado completado cuando se completen todas las tareas secundarias. Las excepciones se propagan a la tarea principal. Necesitará un titular de resultados, ya que se asignará el resultado después de que el delegado de la tarea principal haya finalizado, pero se establecerá cuando se ejecuten las tareas principales de continuación.

De esta manera:

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

Sí, el marco viene con un método útil extensión Separar() para exactamente lo que quiere.

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

Si estás haciendo la cancelación, entonces tendrá que pasar cancelar fichas en los lugares adecuados, obviamente.

Cuestiones relacionadas