Должен ли этот код возвращать Task или Task<object>?
-
21-12-2019 - |
Вопрос
я читал Природа TaskCompletionSource, сообщение Стивена Тауба.
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;
}
Поскольку нас больше не волнует, какой тип
T
то есть я по умолчанию использовалObject
.Затем, когдаAction
выполняется успешно,SetResult
до сих пор используется для переходаTask
вRanToCompletion
конечное состояние;однако, поскольку фактическое значение результата не имеет значения,null
используется. Окончательно,RunAsync
возвращаетTask
скорее, чемTask<Object>
.Конечно, экземплярtask
тип все ещеTask<Object>
, но нам не нужно ссылаться на него как таковой, и потребителю этого метода не нужно беспокоиться об этих деталях реализации.
Я не особенно понимаю, почему метод должен возвращать Task
скорее, чем Task<object>
(поэтому я подчеркнул жирное предложение).Я знаю, что метод настроен на возврат Task
но tcs
это TaskCompletionSource<Object>
, нет TaskCompletionSource
(что, я думаю, неправильно).
Решение
Не существует универсального TaskCompletionSource
а учитывая, что все, что вам нужно, — это задача без результата, результат не имеет значения.Вызывающая сторона не знает и в данном случае ее не волнует, что Задача на самом деле является Task<object>
, Звонивший только что await
Это так, и получает исключение, если оно есть.Вызывающий не знает фактического результата.
Этому, конечно, способствует тот факт, что Task<T>
наследует от Task
Также часто можно найти Task<bool>
который возвращает false, или Task<int>
с 0.
Другие советы
Не существует неуниверсального TaskCompletionSource
класс для создания экземпляров Task
которые не являются экземплярами Task<T>
.Это оставляет два варианта параметра универсального типа для TaskCompletionSource<T>
когда вас не волнует (или не предоставляете) возвращаемое значение:
- Используйте произвольный существующий тип, например
object
, в качестве возвращаемого типа.Установите значениеnull
для обозначения завершения задачи. - Используйте определенный закрытый тип и установите значение
null
для обозначения завершения задачи.
Когда я создаю TaskCompletionSource<T>
экземпляр с целью предоставления Task
без возвращаемого значения, я предпочитаю использовать выделенный закрытый тип, чтобы гарантировать, что потребляющий код не ошибется с возвращаемым значением. Task
как пример Task<T>
где результат имеет значение.
Сначала я определяю следующий класс (это может быть private sealed class
если он вложен в другой тип):
internal sealed class VoidResult
{
}
Тогда вместо использования TaskCompletionSource<object>
для источника завершения я использую TaskCompletionSource<VoidResult>
.Поскольку VoidResult
тип недоступен при вызове кода, пользователь не сможет привести Task
возражать против экземпляра Task<VoidResult>
.
Я не особенно понимаю, почему метод должен возвращать
Task
скорее, чемTask<object>
Потому что когда ты вернешься Task<Object>
это означает, что когда этот метод завершится, он выдаст какое-то полезное значение типа Object
.в данном случае мы не даем никакого результата. Вот почему Стивен решил вернуться. Task
.
Если мы имеем дело с Func<Object>
потом возвращаюсь Task<Object>
было бы уместно, так как Func
даст некоторый результат, мы можем решить вернуть его.
Почему
TaskCompletionSource<Object>
, нетTaskCompletionSource
?
Потому что такого нет.Не существует нестандартного TaskCompletionSource
.
Если вы вернули Task<object>
, затем var result = await RunAsync(...)
всегда возвращался бы null
, поскольку именно для этого вы устанавливаете результат.
Клиента это не волнует, поэтому вы просто возвращаете Task
.
В идеале вы должны использовать TaskCompletionSource
внутри, вместо TaskCompletionSource<object>
, и просто позвоните что-то вроде SetCompleted()
вместо SetResult(null)
.Но такого типа не существует.