Есть список задач, каждую задачу нужно ограничить по времени. Если задача не успевает выполниться, то пусть возвращается какое-то дефолтное значение.
class Program
{
static void Main(string[] args)
{
var sw = new Stopwatch();
sw.Start();
foreach (var i in GetInts())
{
Console.WriteLine("result: " + i);
}
sw.Stop();
Console.WriteLine("done " + sw.Elapsed.TotalMilliseconds.ToString("F2"));
Console.ReadKey();
}
static List
tasks[1] = Task.Run(() =>
{
int i = 5000;
Thread.Sleep(i);
Console.WriteLine(i);
return i;
}, cts.Token);
tasks[0] = Task.Run(() =>
{
int i = 3000;
Thread.Sleep(i);
Console.WriteLine(i);
return i;
}, cts.Token);
tasks[2] = Task.Run(() =>
{
int i = 2000;
Thread.Sleep(i);
Console.WriteLine(i);
return i;
}, cts.Token);
var delay = 1000;
return tasks.Select(x => x.TimeoutAfter(delay, cts)).Select(x => x.Result).ToList();
}
}
}
public static class TaskExtensions
{
public static async Task
using (var timeoutCancellationTokenSource = new CancellationTokenSource())
{
var completedTask = await Task.WhenAny(task, Task.Delay(delay, timeoutCancellationTokenSource.Token));
if (completedTask == task)
{
timeoutCancellationTokenSource.Cancel();
return await task;
}
else
{
cts.Cancel();
return default(TResult);
}
}
}
}
Хотя в коде прописано ограничение в 1000мс, данный код выводит следующий результат:
2000
result: 0
result: 0
result: 2000
done 2010,83
3000
5000
Почему задача с 2с попадает в result? А если выставить delay = 3000, то выведутся все задачи (хотя 5с не должна):
2000
3000
5000
result: 3000
result: 5000
result: 2000
done 5008,06
Ответ
Смотрите. Вы не можете завершить бегущую задачу насильно, точно так же как вы не можете завершить насильно любой другой код. Для этого задача должна сотрудничать.
Идея с использованием CancellationToken'а правильная, но не доведена до конца, отсюда и проблема. CancellationToken в Task.Run относится только к процессу запуска таска*. Когда таск запущен, он не сможет магическим образом узнать о том, что токен сработал, ему нужно самому проверять отмену токена.
Например, так:
var ct = cts.Token;
tasks[0] = Task.Run(async () => // добавил async
{
int i = 3000;
await Task.Delay(i, ct); // проверяем токен
Console.WriteLine(i);
return i;
}, ct);
Ещё по теме: Как работает CancellationToken в TaskFactory.StartNew Method (Action, CancellationToken)?
*Почти. Ещё он ассоциируется с таском, и при выбросе исключения этим токеном таск переходит в состояние Cancelled, а не Faulted
Комментариев нет:
Отправить комментарий