특별한 규칙 으로 Task <T> 가 완료 되기를 기다립니다 . X 밀리 초 후에 완료되지 않은 경우 사용자에게 메시지를 표시하고 싶습니다. 그리고 Y 밀리 초 후에 완료되지 않으면 자동으로 취소를 요청 하고 싶습니다 .
Task.ContinueWith 를 사용 하여 작업이 완료 될 때까지 비동기 적으로 기다릴 수 있지만 (예 : 작업이 완료되면 작업이 실행되도록 예약) 시간 초과를 지정할 수 없습니다. Task.Wait 를 사용 하여 작업이 시간 초과로 완료 될 때까지 동 기적으로 기다릴 수 있지만 스레드가 차단됩니다. 작업이 시간 초과로 완료 될 때까지 비동기식으로 기다리는 방법은 무엇입니까?
답변
이건 어때요:
int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
// task completed within timeout
} else {
// timeout logic
}
추가 : 내 답변에 대한 의견을 요청하면 취소 처리가 포함 된 확장 솔루션이 있습니다. 작업과 타이머에 취소를 전달하면 코드에서 취소가 발생할 수있는 여러 가지 방법이 있다는 것을 의미하며 모든 테스트를 올바르게 처리하고 확신해야합니다. 다양한 조합의 기회를 놓치지 말고 컴퓨터가 런타임에 올바르게 작동하기를 바랍니다.
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.");
}
}
}
답변
Task.WaitAny
여러 작업 중 첫 번째 작업을 기다리는 데 사용할 수 있습니다 .
지정된 시간 초과 후 완료되는 두 개의 추가 작업을 만든 다음 WaitAny
먼저 완료 될 때까지 기다리는 데 사용할 수 있습니다. 먼저 완료된 작업이 “작업”작업이면 완료된 것입니다. 처음 완료된 작업이 시간 초과 작업 인 경우 시간 초과에 반응 할 수 있습니다 (예 : 요청 취소).
답변
이런 건 어때?
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 옵션을 사용할 수 있습니다.
답변
다음은 최상위 투표 답변을 기반으로 한 완전한 예입니다.
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 초) 동안 중단 된 경우 오류가 표시되고 계속 진행되거나 다시 시도됩니다.
답변
Stephen Cleary의 탁월한 AsyncEx 라이브러리를 사용하여 다음을 수행 할 수 있습니다.
TimeSpan timeout = TimeSpan.FromSeconds(10);
using (var cts = new CancellationTokenSource(timeout))
{
await myTask.WaitAsync(cts.Token);
}
TaskCanceledException
시간 초과가 발생하면 발생합니다.
답변
이것은 이전 답변의 약간 향상된 버전입니다.
- 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;
}
