Я искал способ определения тайм-аута задачи, основанный на задачах, без необходимости Wait() для этого.

Тайм-аут должен быть помещен на самую внутреннюю задачу в цепочке продолжений, в то время как исключение должно перехватывать только на самом внешнем уровне.

Мое решение вместо блокировки выполнения возвращает задачу, которая обертывает исходную, что позволяет пользователю перехватить исключение в случае тайм-аута.

Итак, я придумал этот код:

public static Task<T> Timeout<T>(Task<T> baseTask,  int milliseconds)
{
    var timer = Delay(milliseconds);

    return Task.Factory.ContinueWhenAny(
        new []
        {
            baseTask,
            timer.ContinueWith<T>(x => { throw new TaskTimeOutException(); })
        },
        x => x.Result
    );
}

Функция Delay() описана в принятом решении по адресу Как перевести задачу в спящий режим (или отложить) в C # 4.0?.

Я хотел бы улучшить свой код, в основном у меня есть несколько вопросов:

  • Может кто-нибудь видит возможные подводные камни?
  • Должен ли тайм-аут вызывать отмену исходной задачи?

Спасибо.

РЕДАКТИРОВАТЬ

На основе комментариев я разработал это небольшое улучшение:

public static Task<T> Timeout<T>(Task<T> baseTask,  int milliseconds)
{
    var timer = Delay(milliseconds);

    return Task.Factory.ContinueWhenAny(
        new []
        {
            baseTask,
            timer
        },
        x =>
        {
            if (x.Equals(baseTask)) return baseTask.Result;
            throw new TaskTimeOutException();
        }
    );
}
0
Andrea Rossini 19 Апр 2016 в 10:16

2 ответа

Лучший ответ

Вы можете попробовать создать задачу с помощью CancellationToken, а затем вызвать tokenSource.CancelAfter(...) вот так

var tokeSource = new CancellationTokenSource();
Task.Run(() => { Console.WriteLine("processing"); }, tokenSource.Token);
tokenSource.CancelAfter(TimeSpan.FromSeconds(30));

В .Net 4.0 вы можете реализовать CancelAfter самостоятельно с помощью чего-то вроде

public static class CancellationTokenSourceExtensions
{
    public static Task CancelAfter(this CancellationTokenSource cts, TimeSpan timeout)
    {
        return Task.Delay(timeout).ContinueWith(t => cts.Cancel());
    }
}

Я лично считаю, что решение на основе cts больше соответствует духу TPL.

1
Maxim Kosov 19 Апр 2016 в 14:07
class Program
    {
        static bool continueRun = true;
        static void Main(string[] args)
        {
            System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;

            try
            {
                Console.WriteLine("Enter code");
                Task.Run(() =>
                {
                    Console.WriteLine("Enter task");
                    System.Threading.Thread.Sleep(1000);
                    Console.WriteLine("End thread sleep");
                    throw new Exception("Inner task execution");
                });
                Console.WriteLine("Exit code");
            }
            catch (Exception err)
            {
                Console.WriteLine("Exception code");
            }
            finally
            {
                Console.WriteLine("Finally code");
            }


            Console.WriteLine("Press a key to exit");
            Console.ReadLine();
        }

        private static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e)
        {
            Console.Write("Unhandled exception");
            continueRun = false;
            Console.ReadLine();
        }
    }

Выход:

Enter code
Exit  code
Finally code
Press a key to exit
Enter Task
End thread sleep

В Visual Studio вы увидите возникновение одного неуловленного исключения

0
Othello .net dev 19 Апр 2016 в 12:32