Как объясняли другие в комментариях, исходный фрагмент кода async
блокировался с использованием Task.Wait
и Task.Result
. Это может вызвать проблемы , и этого не следует делать.
Использование Task.Run
приводило к увеличению времени выполнения, которое вы видели, поскольку пул потоков истощался с увеличивающееся количество последовательных вызовов.
Если вы хотите запустить Task
с тайм-аутом, без блокировки, вы можете использовать следующий метод:
public static async Task<(bool hasValue, TResult value)> WithTimeout<TResult>(Task<TResult> task, int msTimeout)
{
using var timeoutCts = new CancellationTokenSource();
var timeoutTask = Task.Delay(msTimeout, timeoutCts.Token);
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == task)
{
timeoutCts.Cancel(); //Cancel timeoutTask
return (true, await task); //Get result or propagate exception
}
//completedTask was our timeoutTask
return (false, default);
}
Этот метод объединяет Task.WhenAny
и Task.Delay
, чтобы запустить задачу тайм-аута вместе с переданным аргументом задачи. Если переданная задача завершится до истечения времени ожидания, будет возвращен результат. Однако, если тайм-аут завершается первым, возвращается (hasValue: false, value: default)
.
Использование в вашем примере:
var task = new WaitingService.ServiceClient().GetDataAsync(sleep);
var result = await WithTimeout(task, 1000);
if (result.hasValue)
{
//Do something with result.value;
}
Важно отметить, что, поскольку ваша задача не поддерживает отмену, она будет продолжена бежать. Если он не обрабатывается где-то еще, он может вызвать исключение, которое не было перехвачено.
Поскольку мы ожидаем выполнения задач через неблокирующий Task.WhenAny
, вы не должны исчерпывать пул потоков, как вы видели в исходный пример.
В вашем последнем примере есть вероятность ненужной дополнительной задержки 0-50 мс перед продолжением работы с результатом. Этот метод вернется немедленно, когда задача завершится в течение периода ожидания.