c# - tiempo - ¿Cuál es la forma correcta de encadenar tareas al devolver una tarea?
programar tarea en c# (3)
Lo estoy tanto con el uso de Tareas en C #, pero me confundo cuando intento devolver una Tarea desde un método y ese método hará múltiples tareas dentro de sí mismo. Entonces, ¿tengo mi método para girar una nueva tarea y luego hacer todo secuencialmente dentro de allí? Es difícil envolver mi cabeza haciendo todo con .ContinuarCon ()
Ejemplo:
public Task<string> GetSomeData(CancellationToken token)
{
return Task.Factory.StartNew(() =>
{
token.ThrowIfCancellationRequested();
var initialData = GetSomeInteger(token).Result;
return GetSomeString(initialData, token).Result;
});
}
public Task<int> GetSomeInteger(CancellationToken token)
{
return Task<int>.Factory.StartNew(() =>
{
return 4;
}, token);
}
public Task<string> GetSomeString(int value, CancellationToken token)
{
return Task<string>.Factory.StartNew(() =>
{
return value.ToString();
}, token);
}
No estoy seguro de cómo escribir este método para que use las Tareas correctamente. Supongo que siento que debería haber un .ContinuarWith allí o algo así.
Posible solución ??
public Task<string> GetSomeData(CancellationToken token)
{
return GetSomeInteger(token).ContinueWith((prevTask) =>
{
return GetSomeString(prevTask.Result, token);
}, token).Unwrap();
}
Aquí hay un método de extensión que construí para resolver esto. Trabaja en .Net 4+
public static Task<TNewResult> ContinueWith<T, TNewResult>(this Task<T> task, Func<Task<T>, Task<TNewResult>> continuationFunction, CancellationToken cancellationToken)
{
var tcs = new TaskCompletionSource<TNewResult>();
task.ContinueWith(t =>
{
if (cancellationToken.IsCancellationRequested)
{
tcs.SetCanceled();
}
continuationFunction(t).ContinueWith(t2 =>
{
if (cancellationToken.IsCancellationRequested || t2.IsCanceled)
{
tcs.TrySetCanceled();
}
else if (t2.IsFaulted)
{
tcs.TrySetException(t2.Exception);
}
else
{
tcs.TrySetResult(t2.Result);
}
});
});
return tcs.Task;
}
En general, a menudo es mejor tratar de evitar la creación de nuevas tareas si ya está trabajando con métodos basados en tareas. El encadenamiento de tareas en lugar de bloquear explícitamente reducirá la sobrecarga del sistema, ya que no mantendrá atado a un hilo de ThreadPool.
Dicho esto, a menudo es más simple bloquear solo lo que estás haciendo.
Tenga en cuenta que C # 5 hace esto mucho más simple, al proporcionar una API que le ofrece lo mejor de ambos:
public async Task<string> GetSomeData(CancellationToken token)
{
token.ThrowIfCancellationRequested();
var initialData = await SomeOtherMethodWhichReturnsTask(token);
string result = await initialData.MethodWhichAlsoReturnsTask(token);
return result;
};
Editar después de la actualización:
Dado el nuevo código, no hay una manera fácil de encadenar esto directamente con ContinueWith
. Hay un par de opciones. Puede usar msdn.microsoft.com/en-us/library/dd780917.aspx para convertir la Task<Task<string>>
que crearía, es decir:
public Task<string> GetSomeData(CancellationToken token)
{
Task<Task<string>> task = GetSomeInteger(token)
.ContinueWith(t =>
{
return GetSomeString(t.Result, token);
}, token);
return task.Unwrap();
}
Alternativamente, puede manejar el desenvolvimiento de forma elegante con TaskCompletionSource<T>
:
public Task<string> GetSomeData(CancellationToken token)
{
var tcs = new TaskCompletionSource<string>();
Task<int> task1 = GetSomeInteger(token);
Task<Task<string>> task2 = task1.ContinueWith(t => GetSomeString(t.Result, token));
task2.ContinueWith(t => tcs.SetResult(t.Result.Result));
return tcs.Task;
}
Esto permite que todo el proceso funcione sin crear una nueva tarea (que vincula un hilo de threadpool), y sin bloquear nunca.
Tenga en cuenta que probablemente desee agregar continuaciones en la cancelación, y use tcs.SetCancelled cuando también se solicitó una cancelación.
Sí, todo se ejecutará secuencialmente dentro de su tarea principal. Esto se debe a que llamar a la propiedad Resultado bloqueará el hilo actual hasta que el valor haya regresado.