tasks programming parallel net examples example asp c# .net .net-4.0 task-parallel-library

c# - programming - Encadenando dos funciones()-> Tarea<A> y A-> Tarea<B>



task c# example (4)

No sé si estoy pensando de forma incorrecta sobre TPL, pero tengo dificultades para entender cómo obtener lo siguiente:

Tengo dos funciones

Task<A> getA() { ... } Task<B> getB(A a) { ... }

Esto parece ocurrir a menudo: puedo obtener asincrónicamente una A. Y con una A, puedo obtener asincrónicamente una B.

No puedo descifrar la forma correcta de encadenar estas funciones juntas en TPL.

Aquí hay un intento:

Task<B> Combined() { Task<A> ta = getA(); Task<Task<B>> ttb = ta.ContinueWith(a => getB(a.Result)); return ttb.ContinueWith(x => x.Result.Result); }

El ContinueWith es donde me confundo. El tipo devuelto es una "Tarea doble", Task<Task<B>> . Esto de alguna manera me parece incorrecto.

ACTUALIZACIÓN 30-09-2011:

Por coincidencia, encontré el método de extensión TaskExtensions.Unwrap que opera en una Task<Task<T>> para dar una Task<T> . Hasta que obtengamos C # 5.0, puedo hacer ta.ContinueWith (a => ...). UnWrap () en situaciones como esta donde la continuación en sí misma devuelve una tarea.


¿Su getB tiene que ser un método que devuelva la Task<B> lugar de B ?

El problema es que ContinueWith es:

public Task<TNewResult> ContinueWith<TNewResult>( Func<Task<TResult>, TNewResult> continuationFunction, CancellationToken cancellationToken )

Entonces, en su caso, porque getB devuelve la Task<B> , está pasando un Func<Task<A>, Task<B>> , entonces TNewResult es la Task<B> .

Si puede cambiar getB para simplemente devolver una B con una A , eso funcionaría ... o podría usar:

return ta.ContinueWith(a => getB(a.Result).Result);

Entonces la expresión lambda será del tipo, Func<Task<A>, B> entonces ContinueWith devolverá una Task<B> .

EDITAR: En C # 5 podrías escribir fácilmente:

public async Task<B> CombinedAsync() { A a = await getA(); B b = await getB(a); return b; }

... así que es "solo" cuestión de averiguar cómo termina eso. Sospecho que es algo como esto, pero con manejo de errores:

public Task<B> CombinedAsync() { TaskCompletionSource<B> source = new TaskCompletionSource(); getA().ContinueWith(taskA => { A a = taskA.Result; Task<B> taskB = getB(a); taskB.ContinueWith(t => source.SetResult(t.Result)); }); return source.Task; }

¿Tiene sentido?


En caso de que esté familiarizado con LINQ (y el concepto de Monad que lo respalda), a continuación encontrará una mónada Tarea simple que le permitirá componer las Tareas.

Implementación de Monad:

public static class TaskMonad { public static Task<T> ToTask<T>(this T t) { return new Task<T>(() => t); } public static Task<U> SelectMany<T, U>(this Task<T> task, Func<T, Task<U>> f) { return new Task<U>(() => { task.Start(); var t = task.Result; var ut = f(t); ut.Start(); return ut.Result; }); } public static Task<V> SelectMany<T, U, V>(this Task<T> task, Func<T, Task<U>> f, Func<T, U, V> c) { return new Task<V>(() => { task.Start(); var t = task.Result; var ut = f(t); ut.Start(); var utr = ut.Result; return c(t, utr); }); } }

Ejemplo de uso:

public static void Main(string[] arg) { var result = from a in getA() from b in getB(a) select b; result.Start(); Console.Write(result.Result); }


Si bien la respuesta aceptada probablemente funcione

Task<B> Combined() { Task<A> ta = getA(); Task<B> ttb = ta.ContinueWith(a => getB(a.Result)).Unwrap(); return ttb; }

Es una forma mucho más elegante de implementar esto.


Si no puede usar await , puede usar Unwrap , pero maneja las excepciones de manera subóptima. El método que prefiero es Then como se describe en este artículo . La composición se vuelve simple y elegante:

Task<B> Combined() { return getA().Then(getB); }

Para aquellos interesados ​​en los detalles, hace un tiempo escribí una publicación en el blog sobre exactamente este problema de composición de métodos asíncronos, y cómo las mónadas proporcionan una solución elegante.