programing

작업이 시간 초과와 함께 완료될 때까지 비동기적으로 대기

testmans 2023. 5. 10. 20:32
반응형

작업이 시간 초과와 함께 완료될 때까지 비동기적으로 대기

작업을 기다리고 싶습니다. <몇 가지 특별한 규칙으로 완료하기:X밀리초가 지나도 완료되지 않으면 사용자에게 메시지를 표시하고 싶습니다.그리고 Y밀리초가 지나도 완료되지 않으면 자동으로 취소 요청을 하고 싶습니다.

작업을 사용할 수 있습니다.계속에서 작업이 완료될 때까지 비동기적으로 대기(즉, 작업이 완료될 때 실행되도록 작업을 예약)하지만 시간 제한을 지정할 수는 없습니다.작업을 사용할 수 있습니다.작업이 시간 초과와 함께 완료될 때까지 동시에 대기하지만 이 경우 스레드가 차단됩니다.작업이 완료되고 시간이 초과될 때까지 비동기적으로 기다리는 방법은 무엇입니까?

이거 어때:

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"라는 멋진 블로그 게시물이 있습니다.(MS Parallel Library team의) 이러한 종류의 것에 대한 자세한 내용은 다음과 같습니다.

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

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
}

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.");
        }
    }
}

.Net 6(Preview 7) 이상부터는 새로운 내장 메서드 태스크가 있습니다.Async가 이를 달성할 때까지 기다립니다.

// Using TimeSpan
await myTask.WaitAsync(TimeSpan.FromSeconds(10));

// Using CancellationToken
await myTask.WaitAsync(cancellationToken);

// Using both TimeSpan and CancellationToken
await myTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken);

이 완료되기 전에 완료되지 않은 경우TimeSpan또는CancellationToken그러면 그것은 던집니다.TimeoutException또는TaskCanceledException각각 다음과 같다.

try
{
    await myTask.WaitAsync(TimeSpan.FromSeconds(10), cancellationToken);

}
catch (TaskCanceledException)
{
    Console.WriteLine("Task didn't get finished before the `CancellationToken`");
}
catch (TimeoutException)
{
    Console.WriteLine("Task didn't get finished before the `TimeSpan`");
}

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

작업된 시간 초과 후 을 " " " " " " " " ( " " " ( " " " ( " " " " " 을 사용할 수 .WaitAny어떤 것이든 먼저 완료되기를 기다립니다.먼저 완료한 작업이 "작업" 작업이면 완료됩니다.먼저 완료한 작업이 시간 초과 작업인 경우 시간 초과에 대응할 수 있습니다(예: 취소 요청).

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

  • 로렌스의 답변 외에도 시간 초과가 발생하면 원래 작업을 취소합니다.
  • sjb의 답변 변형 2 3 이외에도 다음과 같은 기능을 제공할 수 있습니다.CancellationToken할 때, 은 원래작경우, 고시발초면생하가과간의업그리,발면하▁for생,▁occurs▁get가,TimeoutExceptionOperationCanceledException.
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;
}

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

TimeSpan timeout = TimeSpan.FromSeconds(10);

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

TaskCanceledException시간 초과 시 던집니다.

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

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초) 동안 중단된 후 오류를 표시하고 계속 진행하거나 다시 시도합니다.

이런 건 어때요?

    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();
    }

태스크를 사용할 수 있습니다.다른 작업을 사용하여 주 스레드를 차단하지 않고 대기 옵션.

타이머를 사용하여 메시지 및 자동 취소를 처리합니다.작업이 완료되면 타이머가 실행되지 않도록 타이머에 대해 폐기를 호출합니다.다음은 예제입니다. 작업지연을 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);
                }
            }
        }
    }
}

또한 비동기 CTP는 TaskEx를 제공합니다.태스크에서 타이머를 래핑하는 지연 방법입니다.이렇게 하면 타이머가 실행될 때 작업 스케줄러를 계속 설정하는 것과 같은 작업을 더 잘 제어할 수 있습니다.

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;
}

.Net 6(답변 날짜 미리보기 7)을 사용하면 새로운 WaitAsync(시간 범위, 취소)를 사용할 수 있습니다.토큰)은 이러한 특정 요구에 대한 답변입니다.만약 당신이 사용할 수 있다면.Net6, 이 버전은 또한 이 게시물에서 제안된 대부분의 좋은 솔루션과 비교하면 최적화된 것으로 설명됩니다.

(여러 해 동안 귀사의 솔루션을 사용해 왔기 때문에 모든 참가자에게 감사드립니다.)

이 문제를 해결하는 또 다른 방법은 반응형 확장을 사용하는 것입니다.

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;

반응형 확장을 사용하는 위의 @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: 시간 초과가 발생하면 시간 초과 예외가 발생합니다.

재미삼아 'On Timeout'을 Task로 확장했습니다.시간 초과 시 작업은 원하는 인라인 람다 작업()을 실행하고 true를 반환하며 그렇지 않으면 false를 반환합니다.

public static async Task<bool> OnTimeout<T>(this T t, Action<T> action, int waitms) where T : Task
{
    if (!(await Task.WhenAny(t, Task.Delay(waitms)) == t))
    {
        action(t);
        return true;
    } else {
        return false;
    }
}

OnTimeout 확장은 UDP 소켓 비동기를 호출하는 예제와 같이 변수에 할당할 수 있는 bool 결과를 반환합니다.

var t = UdpSocket.ReceiveAsync();

var timeout = await t.OnTimeout(task => {
    Console.WriteLine("No Response");
}, 5000);

'task' 변수는 시간 초과 람다에서 추가 처리를 위해 액세스할 수 있습니다.

객체 수신 작업을 사용하면 다른 다양한 확장 설계에 영향을 줄 수 있습니다.

작업 또는 지연이 완료될 때까지 대기할 확장을 만듭니다.지연이 승리할 경우 예외를 던집니다.

public static async Task<TResult> WithTimeout<TResult>(this Task<TResult> task, TimeSpan timeout)
{
    if (await Task.WhenAny(task, Task.Delay(timeout)) != task)
        throw new TimeoutException();
    return await task;
}

나는 느꼈습니다.Task.Delay() 및 스크및CancellationTokenSource다른 답변에서는 엄격한 네트워킹 루프에서 제 사용 사례에 대해 약간 많은 답변을 제공합니다.

비록 호그가 임무를 짜고 있지만 말입니다.MSDN 블로그의 방법이 영감을 준 후 타임아웃을 사용하는 것에 약간 싫증이 났습니다.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;

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

그래서 이것은 오래된 것이지만, 훨씬 더 나은 현대적인 해결책이 있습니다.어떤 버전의 c#/인지 확실하지 않습니다.NET이 필요하지만 다음과 같이 해야 합니다.


... Other method code not relevant to the question.

// a token source that will timeout at the specified interval, or if cancelled outside of this scope
using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(5));
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token, timeoutTokenSource.Token);

async Task<MessageResource> FetchAsync()
{
    try
    {
        return await MessageResource.FetchAsync(m.Sid);
    } catch (TaskCanceledException e)
    {
        if (timeoutTokenSource.IsCancellationRequested)
            throw new TimeoutException("Timeout", e);
        throw;
    }
}

return await Task.Run(FetchAsync, linkedTokenSource.Token);

CancellationTokenSource생성자가 사용합니다.TimeSpan매개 변수입니다. 이 매개 변수는 해당 간격이 경과한 후 해당 토큰을 취소합니다.그런 다음 비동기(또는 동기) 코드를 다른 호출로 감쌀 수 있습니다.Task.Run시간 초과 토큰을 전달합니다.

이것은 당신이 취소 토큰을 전달한다고 가정합니다.token변수).시간 초과와 별도로 작업을 취소할 필요가 없는 경우 다음을 사용할 수 있습니다.timeoutTokenSource직접적으로.그렇지 않으면 다음을 생성합니다.linkedTokenSource시간 초과가 발생하거나 그렇지 않으면 취소됩니다.

그러면 우리는 그냥 잡습니다.OperationCancelledException그리고 어떤 토큰이 예외를 던졌는지 확인하고, 던집니다.TimeoutException시간 초과로 인해 문제가 발생한 경우.그렇지 않으면, 우리는 버려집니다.

또한 여기서는 C# 7에 소개된 로컬 함수를 사용하고 있지만 람다 함수나 실제 함수를 동일한 효과로 쉽게 사용할 수 있습니다.마찬가지로, c#8은 문을 사용하기 위한 더 간단한 구문을 도입했지만, 문을 다시 쓰기에 충분히 쉽습니다.

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

저는 여기에 있는 다른 답변들과 다른 스레드에 있는답변들의 아이디어를 Try 스타일의 확장 방법으로 재조합하고 있습니다.이것은 확장 방법을 원하지만 시간 초과 시 예외가 발생하지 않도록 하는 경우에 유용합니다.

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);
    }
}    

누군가 이런 것을 찾고 있는 경우(OP의 질문 이후 12년).

다른 옵션은 작업만 사용하는 것입니다.다른 작업 내부에서 대기(시간 초과)합니다.실행(). 작업을 사용하지 않으려면 이 옵션을 선택합니다.WaitAny() 또는 대기 호출.아니면 제 경우에는 제가 함께 작업하고 있는 나머지 .cs와의 일관성을 위해서야.

이와 같은 것:

        int timeout = 5000;
        var actualTask = new Task(() =>
        {
            // Do your stuff here
        });

        Task.Run(() =>
        {
            actualTask.Start();
            if (!actualTask.Wait(timeout))
            {
                return false;
                // or throw new TimeoutException("Operation timed out!");
            }

            return true;
        }).ContinueWith((timedTaskResult) =>
        {
            if (!timedTaskResult.Result)
            {
                // tell user it timed out!
            }

            if (timedTaskResult.IsFaulted)
            {
                // Tell the user about the error/s via the timedTaskResult.Exception
            }
        });

언급URL : https://stackoverflow.com/questions/4238345/asynchronously-wait-for-taskt-to-complete-with-timeout

반응형