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

299
26 июня 2017, 17:21

Есть список задач, каждую задачу нужно ограничить по времени. Если задача не успевает выполниться, то пусть возвращается какое-то дефолтное значение.

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<int> GetInts()
    {
        using (var cts = new CancellationTokenSource())
        {
            var tasks = new Task<int>[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<TResult> TimeoutAfter<TResult>(this Task<TResult> 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
Answer 1

Смотрите. Вы не можете завершить бегущую задачу насильно, точно так же как вы не можете завершить насильно любой другой код. Для этого задача должна сотрудничать.

Идея с использованием 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.

READ ALSO
Обработка данных в дочернем окне MVVM

Обработка данных в дочернем окне MVVM

Нужно реализовать добавление продуктов в категории через диалоговое окно

274
Как достать значение из async Task

Как достать значение из async Task

Есть вот такой кодВопрос: как в Main достать значение heart из ExampleTask()?

325
Как подключиться к MySQL БД на хостинге?

Как подключиться к MySQL БД на хостинге?

Доброго дня! Недавно начал изучать программированиеНачал с Unity3d использую C#

443
Настройка Chart в WPFToolkit

Настройка Chart в WPFToolkit

Доброго всем времени суток!

241