Есть ли причина, по которой класс Task не имеет перегрузки Run
, которая принимает задачу?
Да, потому что ее не должно быть.
Ради этого ответа Task
или Task<T>
представляет некоторую операцию, которая выполняет некоторую работу и возвращает значение, которое может существовать или может еще не существовать . Это абстракция для всех видов работы (например, параллельная операция, выполняемая в другом потоке, асинхронная операция ввода-вывода, выполняемая в другом месте на аппаратном уровне, представление синхронной операции или что-то еще.
Что за Task
/ Task<T>
не означает , не представляет собой Func<>
или Action<>
и не представляет собой «шаблон задания», который можно использовать для запуска новой операции (воспринимайте его как представление задания, которое уже началось ).
В частности, Task.Run
: действительный метод Task.Run(Func<>)
/ Task.Run(Action)
в. NET - сокращение от , начиная с a Func<>
или Action<>
в пул потоков планировщика по умолчанию (т.е. одновременно, многопоточный). Вы не можете «перезапустить» Task
(граф конечного автомата Task
строго однонаправлен), вы можете только запустить новый Task
, используя любой Для запуска оригинального Task
использовался механизм. Таким образом, вы не можете произвольно перезапустить асинхронную операцию Socket
, например, потому что это будет означать перемотку состояния всей вашей программы, и это и нарушая физику l aws ...
Если у вас есть объект Task<T>
, то (при условии, что вы используете его правильно), любая операция, которую он представляет, будет уже запланирована или иным образом запущена - или быть уже завершенным - поэтому вы не можете «запустить» Task
, передав его Task.Run
, потому что он уже был запущен (это упрощение).
Пример, который вы привели (reposted) ниже) ничего полезного не делает:
public static Task Run(Task task) => Task.Run(async () => await task);
Я переписал его в более длинной форме ниже, чтобы было легче следить:
public static Task Run(Task originalTask)
{
LambdaCapture capture = new LambdaCapture( originalTask );
Task poolTask = Run( capture.Run ); // Remember that a Delegate includes the `this` reference unlike a raw C-style function-pointer.
return poolTask;
}
// Oversimplified representation of what Task.Run does:
public static Task Run( Action action )
{
ThreadPool pool = GetThreadPoolFromSomewhere();
TaskCompletionSource tsc = new TaskCompletionSource();
Action wrappedAction = () =>
{
// Run the action:
action();
// When it completes, inform TaskCompletionSource:
tsc.SetResult(); // Task (not `Task<T>`) has no result value.
// When `SetResult()` is invoked, the thread running this code will not return to here until after it runs the contination scheduled after `originalTask`.
};
pool.AddJob( wrappedAction ); // Adds `wrappedAction` to a queue which is dequeued by the first available thread.
return tsc.Task; // <-- this is a new Task created by the TaskCompletionSource.
}
private class LambdaCapture
{
private readonly Task originalTask;
public Runnable( Task originalTask )
{
this.originalTask = originalTask;
}
public async Task Run()
{
await this.originalTask;
}
}
Когда вы предлагаете Task.Run(Task)
метод вызывается, он делает это:
- Он запланирует
LambdaCapture.Run
запуск на первом доступном потоке в пуле потоков. - Затем он создаст и вернет отдельный новый экземпляр
Task
для представления операции пула потоков (то есть параллельной операции) независимо от характера originalTask
. - Когда пул потоков становится доступным и рабочий поток запускает
LambdaCapture.Run
, он будет (оверси предупреждение mplification) проверьте, завершено ли originalTask
, и если да, вернет и сообщит планировщику originalTask
, что оно завершено, если нет, то запланирует оставшуюся часть Runnable.Run
(т.е. все код после await
(который в данном случае является просто одним оператором return;
) для выполнения после завершения originalTask
, сделав его продолжением . - Так что, когда
originalTask
завершает (предполагая, что делает выполнено), тогда поток, которому назначено следующее продолжение из originalTask
, затем выполнит оставшуюся часть операции Task.Run
и сообщит планировщику пула рабочих потоков, что это сделано, и затем (предположительно) выполнить продолжение await
из любого кода awaits
Task
, возвращенного из Task.Run
. - Если это сбивает с толку, это потому, что я отстой в объяснении.
Task<T>
в C# работает по существу так же, как Promise<T>
в JavaScript / TypeScript или std::promise
в C ++.
Короче говоря: нет причин для делать то, что вы предлагаете, кроме как тратить циклы ЦП в потоке пула потоков. Как говорит @Fabio, просто сделайте await task
в исходном методе.
Если вы не можете await task
в своем исходном методе, потому что этот метод не является async
методом, тогда даже если Task.Run(Task)
Существовало, что это не поможет, потому что вам все еще нужно await
Task
, которое возвращается Task.Run
.