¿Este código debería devolver una tarea o una tarea <objeto>?
-
21-12-2019 - |
Pregunta
estaba leyendo La naturaleza de la fuente de finalización de tareas, una publicación de Stephen Toub.
public static Task RunAsync(Action action)
{
var tcs = new TaskCompletionSource<Object>();
ThreadPool.QueueUserWorkItem(_ =>
{
try
{
action();
tcs.SetResult(null);
}
catch(Exception exc) { tcs.SetException(exc); }
});
return tcs.Task;
}
Como ya no nos importa cuál sea el tipo de
T
es que he optado por usarObject
.Entonces, cuando elAction
se ejecuta con éxito,SetResult
todavía se utiliza para hacer la transiciónTask
en elRanToCompletion
estado final;sin embargo, dado que el valor del resultado real es irrelevante,null
se utiliza. Finalmente,RunAsync
devolucionesTask
en vez deTask<Object>
.Por supuesto, el instanciadotask
El tipo sigue siendoTask<Object>
, pero no necesitamos referirnos a él como tal, y el consumidor de este método no necesita preocuparse por esos detalles de implementación.
No entiendo particularmente por qué el método debería regresar Task
en vez de Task<object>
(por eso enfaticé la frase en negrita).Sé que el método está configurado para regresar Task
pero tcs
es un TaskCompletionSource<Object>
, no TaskCompletionSource
(lo cual está mal, creo).
Solución
No existe un no genérico. TaskCompletionSource
y considerando que lo único que quieres es una tarea sin resultado, el resultado no importa.La persona que llama no sabe y no le importa en este caso que la Tarea sea en realidad una Task<object>
, La persona que llama simplemente await
Así es y obtiene una excepción si la hay.La persona que llama desconoce el resultado real.
Por supuesto, esto se ve facilitado por el hecho de que Task<T>
hereda de Task
También es común encontrar un Task<bool>
que devuelve falso, o Task<int>
con 0.
Otros consejos
No hay no genérico TaskCompletionSource
clase para crear instancias de Task
que no son casos de Task<T>
.Esto deja dos opciones para el parámetro de tipo genérico para TaskCompletionSource<T>
cuando no le importa (o no proporciona) el valor de retorno:
- Utilice un tipo existente arbitrario, como
object
, como tipo de devolución.Establezca el valor ennull
para indicar la finalización de la tarea. - Utilice un tipo no público específico y establezca el valor en
null
para indicar la finalización de la tarea.
Cuando creo un TaskCompletionSource<T>
instancia con el fin de proporcionar una Task
sin valor de retorno, prefiero usar un tipo no público dedicado para garantizar que el código utilizado no confunda lo devuelto Task
como una instancia de Task<T>
donde el resultado tiene significado.
Primero, defino la siguiente clase (puede ser una private sealed class
si está anidado dentro de otro tipo):
internal sealed class VoidResult
{
}
Entonces, en lugar de utilizar TaskCompletionSource<object>
para la fuente de finalización, uso TaskCompletionSource<VoidResult>
.desde el VoidResult
tipo no es accesible llamando al código, el usuario no podrá transmitir el Task
oponerse a una instancia de Task<VoidResult>
.
No entiendo particularmente por qué el método debería regresar
Task
en vez deTask<object>
porque cuando regreses Task<Object>
significa que cuando este método se complete producirá algún valor útil de tipo Object
.En este caso no estamos produciendo ningún resultado, es por eso que Stephen decidió regresar. Task
.
Si estamos tratando con Func<Object>
luego regresando Task<Object>
Sería apropiado, ya que Func
producirá algún resultado, podemos optar por devolverlo.
Por qué
TaskCompletionSource<Object>
, noTaskCompletionSource
?
Porque no existe tal cosa.No hay no genérico TaskCompletionSource
.
Si devolviste un Task<object>
, entonces var result = await RunAsync(...)
siempre volvería null
, ya que eso es a lo que estás configurando el resultado.
Al cliente no le importa esto, así que simplemente devuelve un Task
.
Lo ideal sería utilizar un TaskCompletionSource
internamente, en lugar de TaskCompletionSource<object>
, y simplemente llama a algo como SetCompleted()
en lugar de SetResult(null)
.Pero ese tipo no existe.