Страницы

Поиск по вопросам

вторник, 19 февраля 2019 г.

Ограничить время жизни потока

Есть список задач, каждую задачу нужно ограничить по времени. Если задача не успевает выполниться, то пусть возвращается какое-то дефолтное значение.
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 GetInts() { using (var cts = new CancellationTokenSource()) { var tasks = new Task[3];
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 TimeoutAfter(this Task task, int delay, CancellationTokenSource cts) {
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

Комментариев нет:

Отправить комментарий