작업 <T>이 시간 초과로 완료 될 때까지 비동기 적으로 대기


387

특별한 규칙 으로 Task <T> 가 완료 되기를 기다립니다 . X 밀리 초 후에 완료되지 않은 경우 사용자에게 메시지를 표시하고 싶습니다. 그리고 Y 밀리 초 후에 완료되지 않으면 자동으로 취소를 요청 하고 싶습니다 .

Task.ContinueWith 를 사용 하여 작업이 완료 될 때까지 비동기 적으로 기다릴 수 있지만 (예 : 작업이 완료되면 작업이 실행되도록 예약) 시간 초과를 지정할 수 없습니다. Task.Wait 를 사용 하여 작업이 시간 초과로 완료 될 때까지 동 기적으로 기다릴 수 있지만 스레드가 차단됩니다. 작업이 시간 초과로 완료 될 때까지 비동기식으로 기다리는 방법은 무엇입니까?


3
네 말이 맞아 시간 초과를 제공하지 않는 것이 놀랍습니다. 아마도 .NET 5.0에서 ... 물론 우리는 작업 자체에 타임 아웃을 구축 할 수는 있지만 좋지 않습니다.
Aliostad

4
설명하는 2 계층 타임 아웃에는 여전히 로직이 필요하지만 .NET 4.5는 실제로 타임 아웃 기반을 생성하는 간단한 방법을 제공합니다 CancellationTokenSource. 생성자에 대한 두 가지 과부하를 사용할 수 있습니다. 하나는 정수 밀리 초 지연이고 다른 하나는 TimeSpan 지연입니다.
patridge

완전한 간단한 lib 소스는 여기에 있습니다 : stackoverflow.com/questions/11831844/…

전체 소스 코드가 작동하는 최종 솔루션? 각 스레드의 알림 오류 및 WaitAll이 요약을 표시 한 후 더 복잡한 샘플일까요?
Kiquenet

답변:


563

이건 어때요:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

그리고 여기 에 이런 종류의 일에 대한 자세한 정보가 담긴 훌륭한 블로그 게시물 인 "Crafting a Task.TimeoutAfter Method"(MS Parallel Library 팀)가 있습니다 .

추가 : 내 답변에 대한 의견을 요청하면 취소 처리가 포함 된 확장 솔루션이 있습니다. 작업과 타이머에 취소를 전달하면 코드에서 취소가 발생할 수있는 여러 가지 방법이 있다는 것을 의미하며 모든 테스트를 올바르게 처리하고 확신해야합니다. 다양한 조합의 기회를 놓치지 말고 컴퓨터가 런타임에 올바르게 작동하기를 바랍니다.

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}

86
Task.Delay가 장시간 실행되는 작업 전에 완료되어 시간 초과 시나리오를 처리 할 수 ​​있지만 장기 실행 작업 자체는 취소되지 않습니다. WhenAny는 단순히 전달 된 작업 중 하나가 완료되었음을 알려줍니다. CancellationToken을 구현하고 장기 실행 작업을 직접 취소해야합니다.
Jeff Schumacher

30
또한 주목해야 할 수 있습니다 Task.Delay작업이 시간 제한에 관계없이 긴 방법 만료 될 때까지 추적을 계속하는 시스템 타이머에 의해 백업 SomeOperationAsync됩니다. 따라서이 전체 코드 스 니펫이 꽉 찬 루프에서 많이 실행되면 타이머가 모두 시간 초과 될 때까지 시스템 리소스를 소비합니다. 이 문제를 해결하는 방법 은 타이머 리소스 해제가 완료되면 취소 CancellationToken하도록 전달하는 것 Task.Delay(timeout, cancellationToken)입니다 SomeOperationAsync.
Andrew Arnott

12
취소 코드가 너무 많은 작업을 수행하고 있습니다. 이것을 시도하십시오 : int timeout = 1000; var CancellationTokenSource = 새로운 CancellationTokenSource (timeout); var cancelToken = tokenSource.Token; var task = SomeOperationAsync (cancellationToken); {작업을 기다립니다; // 성공적인 완료를 위해 여기에 코드 추가} catch (OperationCancelledException) {// 시간 초과 사례를 위해 여기에 코드 추가}
srm

3
@ilans를 기다리면 Task작업이 저장 한 예외가 해당 시점에서 다시 발생합니다. 이렇게 OperationCanceledException하면 취소 (취소 된 경우) 또는 다른 예외 (오류가 발생한 경우) 를 잡을 수 있습니다.
앤드류 아 노트

3
@ TomexOu : 문제는 작업 완료 를 비동기식으로 기다리는 방법이었습니다 . Task.Wait(timeout)비동기 적으로 기다리는 대신 동 기적으로 차단합니다.
앤드류 아 노트

220

Andrew Arnott가 자신의 답변 에 대한 의견에서 제안한대로 원래 작업이 완료되면 시간 초과가 취소되는 확장 방법 버전이 있습니다.

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) {

    using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {

        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
        if (completedTask == task) {
            timeoutCancellationTokenSource.Cancel();
            return await task;  // Very important in order to propagate exceptions
        } else {
            throw new TimeoutException("The operation has timed out.");
        }
    }
}

8
이 사람에게 투표 해주세요. 우아한 솔루션. 그리고 전화가 반환 유형이 없다면 TResult를 제거하십시오.
Lucas

6
CancellationTokenSource는 일회용이며 using블록에 있어야합니다
PeterM

6
@ It'satrap 작업을 두 번 기다리는 것은 단순히 두 번째 기다리는 결과를 반환합니다. 두 번 실행되지 않습니다. task.Result 두 번 실행될 때 와 같다고 말할 수 있습니다.
M. Mimpen

7
task시간 초과가 발생해도 원래 작업 ( )이 계속 실행됩니까?
jag

6
경미한 개선 기회 : TimeoutException적절한 기본 메시지가 있습니다. "작업 시간이 초과되었습니다."로 재정의 값을 추가하지 않으며 실제로이를 무시할 이유가 있음을 암시하여 약간의 혼란을 야기합니다.
Edward Brey

49

Task.WaitAny여러 작업 중 첫 번째 작업을 기다리는 데 사용할 수 있습니다 .

지정된 시간 초과 후 완료되는 두 개의 추가 작업을 만든 다음 WaitAny먼저 완료 될 때까지 기다리는 데 사용할 수 있습니다. 먼저 완료된 작업이 "작업"작업이면 완료된 것입니다. 처음 완료된 작업이 시간 초과 작업 인 경우 시간 초과에 반응 할 수 있습니다 (예 : 요청 취소).


1
필자가 정말로 존중하는 MVP가이 기술을 사용하는 것을 보았습니다. 받아 들인 대답보다 훨씬 깨끗합니다. 예를 들어 더 많은 표를 얻는 데 도움이 될 것입니다! 나는 그것이 도움이 될 것이라고 확신 할 수있는 충분한 작업 경험이 없다는 것을 제외하고는 자원 봉사를했습니다 :)
GrahamMc

3
하나의 스레드가 차단되지만 문제가 없으면 아무런 문제가 없습니다. 스레드가 차단되지 않았기 때문에 내가 취한 솔루션은 아래 솔루션이었습니다. 정말 좋은 블로그 게시물을 읽었습니다.
JJschk

@ JJschk 당신은 당신이 해결책을 가져 갔다 언급 below.... 그게 뭐야? SO 주문을 기반으로?
BozoJoe

느린 작업을 취소하지 않으려면 어떻게해야합니까? 완료 될 때 처리하고 싶지만 현재 방법에서 돌아옵니다.
Akmal Salikhov

18

이런 건 어때?

    const int x = 3000;
    const int y = 1000;

    static void Main(string[] args)
    {
        // Your scheduler
        TaskScheduler scheduler = TaskScheduler.Default;

        Task nonblockingTask = new Task(() =>
            {
                CancellationTokenSource source = new CancellationTokenSource();

                Task t1 = new Task(() =>
                    {
                        while (true)
                        {
                            // Do something
                            if (source.IsCancellationRequested)
                                break;
                        }
                    }, source.Token);

                t1.Start(scheduler);

                // Wait for task 1
                bool firstTimeout = t1.Wait(x);

                if (!firstTimeout)
                {
                    // If it hasn't finished at first timeout display message
                    Console.WriteLine("Message to user: the operation hasn't completed yet.");

                    bool secondTimeout = t1.Wait(y);

                    if (!secondTimeout)
                    {
                        source.Cancel();
                        Console.WriteLine("Operation stopped!");
                    }
                }
            });

        nonblockingTask.Start();
        Console.WriteLine("Do whatever you want...");
        Console.ReadLine();
    }

다른 작업을 사용하여 메인 스레드를 차단하지 않고 Task.Wait 옵션을 사용할 수 있습니다.


실제로이 예제에서는 t1 내부가 아니라 상위 작업을 기다리고 있습니다. 좀 더 자세한 예를 만들어 보겠습니다.
as-cii

14

다음은 최상위 투표 답변을 기반으로 한 완전한 예입니다.

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

이 답변에서 구현의 주요 장점은 제네릭이 추가되어 함수 (또는 작업)가 값을 반환 할 수 있다는 것입니다. 즉, 기존 함수는 다음과 같은 시간 초과 함수로 래핑 될 수 있습니다.

전에:

int x = MyFunc();

후:

// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));

이 코드에는 .NET 4.5가 필요합니다.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskTimeout
{
    public static class Program
    {
        /// <summary>
        ///     Demo of how to wrap any function in a timeout.
        /// </summary>
        private static void Main(string[] args)
        {

            // Version without timeout.
            int a = MyFunc();
            Console.Write("Result: {0}\n", a);
            // Version with timeout.
            int b = TimeoutAfter(() => { return MyFunc(); },TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", b);
            // Version with timeout (short version that uses method groups). 
            int c = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", c);

            // Version that lets you see what happens when a timeout occurs.
            try
            {               
                int d = TimeoutAfter(
                    () =>
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(123));
                        return 42;
                    },
                    TimeSpan.FromSeconds(1));
                Console.Write("Result: {0}\n", d);
            }
            catch (TimeoutException e)
            {
                Console.Write("Exception: {0}\n", e.Message);
            }

            // Version that works on tasks.
            var task = Task.Run(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return 42;
            });

            // To use async/await, add "await" and remove "GetAwaiter().GetResult()".
            var result = task.TimeoutAfterAsync(TimeSpan.FromSeconds(2)).
                           GetAwaiter().GetResult();

            Console.Write("Result: {0}\n", result);

            Console.Write("[any key to exit]");
            Console.ReadKey();
        }

        public static int MyFunc()
        {
            return 42;
        }

        public static TResult TimeoutAfter<TResult>(
            this Func<TResult> func, TimeSpan timeout)
        {
            var task = Task.Run(func);
            return TimeoutAfterAsync(task, timeout).GetAwaiter().GetResult();
        }

        private static async Task<TResult> TimeoutAfterAsync<TResult>(
            this Task<TResult> task, TimeSpan timeout)
        {
            var result = await Task.WhenAny(task, Task.Delay(timeout));
            if (result == task)
            {
                // Task completed within timeout.
                return task.GetAwaiter().GetResult();
            }
            else
            {
                // Task timed out.
                throw new TimeoutException();
            }
        }
    }
}

경고

이 대답을 제공하는 데, 그 일반적으로 하지 당신이 절대적으로하지 않는 한 좋은 연습은 정상 작동 중에 코드에서 발생한 예외를합니다 :

  • 예외가 발생할 때마다 매우 무거운 작업,
  • 예외가 엄격한 루프에있는 경우 예외로 인해 100 배 이상 코드가 느려질 수 있습니다.

호출 한 함수를 절대 변경할 수 없어 특정 코드 이후에 시간이 초과되는 경우에만이 코드를 사용하십시오 TimeSpan.

이 답변은 실제로 타임 아웃 매개 변수를 포함하도록 리팩터링 할 수없는 타사 라이브러리 라이브러리를 처리 할 때만 적용됩니다.

강력한 코드를 작성하는 방법

강력한 코드를 작성하려면 일반적인 규칙은 다음과 같습니다.

무한정 차단 될 수있는 모든 단일 작업에는 시간 초과가 있어야합니다.

당신이 경우 하지 않는 이 규칙을 준수 코드는 결국 어떤 이유로, 다음 무기한 차단 실패하고 앱이 바로 영구적으로 중단으로 작업에 타격을 줄 것으로 예상된다.

일정 시간이 지난 후 합리적인 시간 초과가 발생한 경우 앱이 극단적 인 시간 (예 : 30 초) 동안 중단 된 경우 오류가 표시되고 계속 진행되거나 다시 시도됩니다.


11

Stephen Cleary의 탁월한 AsyncEx 라이브러리를 사용하여 다음을 수행 할 수 있습니다.

TimeSpan timeout = TimeSpan.FromSeconds(10);

using (var cts = new CancellationTokenSource(timeout))
{
    await myTask.WaitAsync(cts.Token);
}

TaskCanceledException 시간 초과가 발생하면 발생합니다.


10

이것은 이전 답변의 약간 향상된 버전입니다.

  • Lawrence의 답변 외에도 시간 초과가 발생하면 원래 작업이 취소됩니다.
  • 에 addtion에서 SJB의 대답은 2와 3을 변형 , 당신이 제공 할 수있는 CancellationToken원래의 작업, 그리고 타임 아웃이 발생하면, 당신은 얻을 TimeoutException대신 OperationCanceledException.
async Task<TResult> CancelAfterAsync<TResult>(
    Func<CancellationToken, Task<TResult>> startTask,
    TimeSpan timeout, CancellationToken cancellationToken)
{
    using (var timeoutCancellation = new CancellationTokenSource())
    using (var combinedCancellation = CancellationTokenSource
        .CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
    {
        var originalTask = startTask(combinedCancellation.Token);
        var delayTask = Task.Delay(timeout, timeoutCancellation.Token);
        var completedTask = await Task.WhenAny(originalTask, delayTask);
        // Cancel timeout to stop either task:
        // - Either the original task completed, so we need to cancel the delay task.
        // - Or the timeout expired, so we need to cancel the original task.
        // Canceling will not affect a task, that is already completed.
        timeoutCancellation.Cancel();
        if (completedTask == originalTask)
        {
            // original task completed
            return await originalTask;
        }
        else
        {
            // timeout
            throw new TimeoutException();
        }
    }
}

용법

InnerCallAsync완료하는 데 시간이 오래 걸릴 수 있습니다. CallAsync시간 초과로 래핑합니다.

async Task<int> CallAsync(CancellationToken cancellationToken)
{
    var timeout = TimeSpan.FromMinutes(1);
    int result = await CancelAfterAsync(ct => InnerCallAsync(ct), timeout,
        cancellationToken);
    return result;
}

async Task<int> InnerCallAsync(CancellationToken cancellationToken)
{
    return 42;
}

1
솔루션 주셔서 감사합니다! 당신이 통과해야처럼 보인다 timeoutCancellationdelayTask. 현재 취소를 실행하면 대신 대신 CancelAfterAsync던질 수 있습니다. 원인 이 먼저 완료 될 수 있습니다. TimeoutExceptionTaskCanceledExceptiondelayTask
AxelUser

@AxelUser, 당신 말이 맞아요. 무슨 일이 일어나고 있는지 이해하기 위해 많은 단위 테스트로 한 시간이 걸렸습니다. :) 주어진 두 작업 WhenAny이 동일한 토큰으로 취소 WhenAny되면 첫 번째 작업을 반환 한다고 가정했습니다 . 그 가정은 틀렸다. 답변을 편집했습니다. 감사!
Josef Bláha

정의 된 Task <SomeResult> 함수로 실제로 이것을 호출하는 방법을 알아내는 데 어려움을 겪고 있습니다. 그것을 호출하는 방법의 예를 알아볼 수 있습니까?
jhaagsma

1
@jhaagsma, 예제가 추가되었습니다!
Josef Bláha

@ JosefBláha 대단히 감사합니다! 나는 여전히 람다 스타일 구문으로 머리를 천천히 감싸고 있습니다. 맵시 있는!
jhaagsma

8

용도 타이머 메시지 자동 취소 처리 할 수 있습니다. 작업이 완료되면 타이머에서 Dispose를 호출하여 타이머가 실행되지 않도록합니다. 다음은 예입니다. 다른 경우를 보려면 taskDelay를 500, 1500 또는 2500으로 변경하십시오.

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        private static Task CreateTaskWithTimeout(
            int xDelay, int yDelay, int taskDelay)
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;
            var task = Task.Factory.StartNew(() =>
            {
                // Do some work, but fail if cancellation was requested
                token.WaitHandle.WaitOne(taskDelay);
                token.ThrowIfCancellationRequested();
                Console.WriteLine("Task complete");
            });
            var messageTimer = new Timer(state =>
            {
                // Display message at first timeout
                Console.WriteLine("X milliseconds elapsed");
            }, null, xDelay, -1);
            var cancelTimer = new Timer(state =>
            {
                // Display message and cancel task at second timeout
                Console.WriteLine("Y milliseconds elapsed");
                cts.Cancel();
            }
                , null, yDelay, -1);
            task.ContinueWith(t =>
            {
                // Dispose the timers when the task completes
                // This will prevent the message from being displayed
                // if the task completes before the timeout
                messageTimer.Dispose();
                cancelTimer.Dispose();
            });
            return task;
        }

        static void Main(string[] args)
        {
            var task = CreateTaskWithTimeout(1000, 2000, 2500);
            // The task has been started and will display a message after
            // one timeout and then cancel itself after the second
            // You can add continuations to the task
            // or wait for the result as needed
            try
            {
                task.Wait();
                Console.WriteLine("Done waiting for task");
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Error waiting for task:");
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}

또한 Async CTP 는 타이머를 작업에 래핑하는 TaskEx.Delay 메서드를 제공합니다. 이렇게하면 타이머가 실행될 때 TaskScheduler를 연속으로 설정하는 등의 작업을 더 많이 제어 할 수 있습니다.

private static Task CreateTaskWithTimeout(
    int xDelay, int yDelay, int taskDelay)
{
    var cts = new CancellationTokenSource();
    var token = cts.Token;
    var task = Task.Factory.StartNew(() =>
    {
        // Do some work, but fail if cancellation was requested
        token.WaitHandle.WaitOne(taskDelay);
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task complete");
    });

    var timerCts = new CancellationTokenSource();

    var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
    messageTask.ContinueWith(t =>
    {
        // Display message at first timeout
        Console.WriteLine("X milliseconds elapsed");
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
    cancelTask.ContinueWith(t =>
    {
        // Display message and cancel task at second timeout
        Console.WriteLine("Y milliseconds elapsed");
        cts.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    task.ContinueWith(t =>
    {
        timerCts.Cancel();
    });

    return task;
}

그는 현재 스레드가 차단되는 것을 원하지 않습니다 task.Wait(). 즉, no 입니다.
Cheng Chen

@ 대니 : 그것은 단지 예제를 완성하는 것이 었습니다. ContinueWith 후 작업을 반환하고 실행할 수 있습니다. 더 명확하게 답변을 업데이트하겠습니다.
Quartermeister

2
@ dtb : t1을 Task <Task <Result >>로 만든 다음 TaskExtensions.Unwrap을 호출하면 어떻게됩니까? 내부 람다에서 t2를 반환 할 수 있으며 나중에 래핑되지 않은 작업에 연속을 추가 할 수 있습니다.
Quartermeister

대박! 그것은 내 문제를 완벽하게 해결합니다. 감사! 나는 TaskExtensions를 제안하는 것에 대한 귀하의 답변을 받아 들일 수 있기를 원하지만 @ AS-CII가 제안한 솔루션을 사용할 것이라고 생각합니다.
dtb

6

이 문제를 해결하는 또 다른 방법은 Reactive Extensions를 사용하는 것입니다.

public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
        return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

단위 테스트에서 아래 코드를 사용하여 위의 테스트를 수행하십시오.

TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
                {
                    int i = 0;
                    while (i < 5)
                    {
                        Console.WriteLine(i);
                        i++;
                        Thread.Sleep(1000);
                    }
                })
                .TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
                .ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);

scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);

다음 네임 스페이스가 필요할 수 있습니다.

using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;

4

Reactive Extensions를 사용하는 위의 @Kevan 답변의 일반 버전.

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
    return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

선택적 스케줄러 사용시 :

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
    return scheduler is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

BTW : 시간 초과가 발생하면 시간 초과 예외가 발생합니다.


0

BlockingCollection을 사용하여 작업을 예약하면 생산자가 잠재적으로 오래 실행되는 작업을 실행할 수 있으며 소비자는 시간 초과 및 취소 토큰이 내장 된 TryTake 메서드를 사용할 수 있습니다.


무언가를 작성해야하지만 (여기서 독점 코드를 넣고 싶지는 않습니다) 시나리오는 다음과 같습니다. 생산자는 시간 초과 될 수있는 메소드를 실행하고 완료되면 결과를 대기열에 넣는 코드가됩니다. 소비자는 시간 초과와 함께 trytake ()를 호출하고 시간 초과시 토큰을받습니다. 생산자와 소비자 모두 백 라운드 작업이되며 필요한 경우 UI 스레드 디스패처를 사용하여 사용자에게 메시지를 표시합니다.
kns98

0

나는 Task.Delay()작업을 느꼈고 CancellationTokenSource다른 하나는 타이트한 네트워킹 루프에서 유스 케이스에 대해 약간의 대답을했습니다.

그리고 Joe Hoag의 MSDN 블로그 에서 Task.TimeoutAfter 메서드 만들기 가 영감을 주 었지만TimeoutException 위와 같은 이유로 흐름 제어 에 사용하는 것이 약간 지쳤습니다. 시간 초과가 더 자주 예상되지 않기 때문입니다.

그래서 나는 이것과 함께 갔는데, 그것은 블로그에서 언급 된 최적화를 처리합니다.

public static async Task<bool> BeforeTimeout(this Task task, int millisecondsTimeout)
{
    if (task.IsCompleted) return true;
    if (millisecondsTimeout == 0) return false;

    if (millisecondsTimeout == Timeout.Infinite)
    {
        await Task.WhenAll(task);
        return true;
    }

    var tcs = new TaskCompletionSource<object>();

    using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs,
        millisecondsTimeout, Timeout.Infinite))
    {
        return await Task.WhenAny(task, tcs.Task) == task;
    }
}

사용 사례의 예는 다음과 같습니다.

var receivingTask = conn.ReceiveAsync(ct);

while (!await receivingTask.BeforeTimeout(keepAliveMilliseconds))
{
    // Send keep-alive
}

// Read and do something with data
var data = await receivingTask;

0

Andrew Arnott의 답변 중 몇 가지 변형 :

  1. 기존 작업을 기다렸다가 완료 또는 시간 초과 여부를 확인하고 시간 초과가 발생하면 취소하지 않으려는 경우 :

    public static async Task<bool> TimedOutAsync(this Task task, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        if (timeoutMilliseconds == 0) {
            return !task.IsCompleted; // timed out if not completed
        }
        var cts = new CancellationTokenSource();
        if (await Task.WhenAny( task, Task.Delay(timeoutMilliseconds, cts.Token)) == task) {
            cts.Cancel(); // task completed, get rid of timer
            await task; // test for exceptions or task cancellation
            return false; // did not timeout
        } else {
            return true; // did timeout
        }
    }
  2. 시간 초과가 발생하면 작업을 시작하고 작업을 취소하려는 경우 :

    public static async Task<T> CancelAfterAsync<T>( this Func<CancellationToken,Task<T>> actionAsync, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var taskCts = new CancellationTokenSource();
        var timerCts = new CancellationTokenSource();
        Task<T> task = actionAsync(taskCts.Token);
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }
  3. 시간 초과가 발생하면 이미 취소 한 작업이 이미있는 경우 :

    public static async Task<T> CancelAfterAsync<T>(this Task<T> task, int timeoutMilliseconds, CancellationTokenSource taskCts)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var timerCts = new CancellationTokenSource();
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }

또 다른 의견은 타임 아웃이 발생하지 않으면이 버전은 타이머를 취소하므로 여러 호출로 인해 타이머가 쌓이지 않습니다.

sjb


0

여기 다른 답변의 아이디어와 다른 스레드 의 Try-style 확장 방법 에 대한 답변을 추천합니다. 확장 방법을 원하지만 시간 초과시 예외를 피하면 이점이 있습니다.

public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task,
    TimeSpan timeout, Action<TResult> successor)
{

    using var timeoutCancellationTokenSource = new CancellationTokenSource();
    var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token))
                                  .ConfigureAwait(continueOnCapturedContext: false);

    if (completedTask == task)
    {
        timeoutCancellationTokenSource.Cancel();

        // propagate exception rather than AggregateException, if calling task.Result.
        var result = await task.ConfigureAwait(continueOnCapturedContext: false);
        successor(result);
        return true;
    }
    else return false;        
}     

async Task Example(Task<string> task)
{
    string result = null;
    if (await task.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), r => result = r))
    {
        Console.WriteLine(result);
    }
}    

-3

확실히 이것을하지는 않지만 ...의 경우에는 유효한 이유를 생각할 수 없습니다.

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.