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.