Este código deve retornar uma Tarefa ou Tarefa<objeto>?
-
21-12-2019 - |
Pergunta
eu estava lendo A natureza da TaskCompletionFonte, uma postagem 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;
}
Já que não nos importamos mais com o tipo de
T
é, eu optei por usarObject
.Então, quando oAction
é executado com sucesso,SetResult
ainda é usado para fazer a transiçãoTask
noRanToCompletion
estado final;no entanto, uma vez que o valor real do resultado é irrelevante,null
é usado. Finalmente,RunAsync
retornaTask
em vez deTask<Object>
.Claro, o instanciadotask
o tipo de ainda éTask<Object>
, mas não precisamos nos referir a ele como tal, e o consumidor desse método não precisa se preocupar com esses detalhes de implementação.
Eu particularmente não entendo por que o método deveria retornar Task
em vez de Task<object>
(é por isso que enfatizei a frase em negrito).Eu sei que o método está configurado para retornar Task
mas tcs
é um TaskCompletionSource<Object>
, não TaskCompletionSource
(o que está errado, eu acho).
Solução
Não existe um não genérico TaskCompletionSource
e considerando que tudo que você quer é uma tarefa sem resultado, o resultado não importa.O chamador não sabe e não se importa, neste caso, que a Tarefa é na verdade uma Task<object>
, O chamador apenas await
é isso e obtém uma exceção, se houver.O chamador não tem conhecimento do resultado real.
É claro que isso é facilitado pelo fato de que Task<T>
herda de Task
Também é comum encontrar um Task<bool>
que retorna falso, ou Task<int>
com 0.
Outras dicas
Não há não genérico TaskCompletionSource
classe para criar instâncias de Task
que não são casos de Task<T>
.Isso deixa duas opções para o parâmetro de tipo genérico para TaskCompletionSource<T>
quando você não se importa (ou não fornece) o valor de retorno:
- Use um tipo existente arbitrário, como
object
, como o tipo de retorno.Defina o valor comonull
para indicar a conclusão da tarefa. - Use um tipo não público específico e defina o valor como
null
para indicar a conclusão da tarefa.
Quando eu crio um TaskCompletionSource<T>
por exemplo, com a finalidade de fornecer um Task
sem valor de retorno, prefiro usar um tipo não público dedicado para garantir que o código consumido não confunda o retornado Task
como uma instância de Task<T>
onde o resultado tem significado.
Primeiro, defino a seguinte classe (pode ser um private sealed class
se estiver aninhado em outro tipo):
internal sealed class VoidResult
{
}
Então, em vez de usar TaskCompletionSource<object>
para a fonte de conclusão, eu uso TaskCompletionSource<VoidResult>
.Desde o VoidResult
tipo não é acessível chamando o código, o usuário não poderá converter o Task
opor-se a uma instância de Task<VoidResult>
.
Eu particularmente não entendo por que o método deveria retornar
Task
em vez deTask<object>
Porque quando você voltar Task<Object>
isso significa que quando este método for concluído, ele produzirá algum valor útil do tipo Object
.neste caso não estamos produzindo nenhum resultado, é por isso que Stephen optou por retornar Task
.
Se estamos lidando com Func<Object>
então voltando Task<Object>
seria apropriado, como Func
produzirá algum resultado, podemos optar por devolvê-lo.
Por que
TaskCompletionSource<Object>
, nãoTaskCompletionSource
?
Porque não existe tal coisa.Não há nenhum genérico TaskCompletionSource
.
Se você devolveu um Task<object>
, então var result = await RunAsync(...)
sempre voltaria null
, já que é para isso que você está definindo o resultado.
O cliente não se importa com isso, então você apenas retorna um Task
.
O ideal é que você use um TaskCompletionSource
internamente, em vez de um TaskCompletionSource<object>
, e apenas chame algo como SetCompleted()
em vez de SetResult(null)
.Mas esse tipo não existe.