Асинхронно ожидайте завершения задачи<T> с тайм-аутом
Я хочу подождать, пока Задание
Я могу использовать Task.ContinueWith для асинхронного ожидания завершения задачи (т.е. запланировать действие, которое будет выполнено после завершения задачи), но это не позволяет указать время ожидания. Я могу использовать Task.Wait для синхронного ожидания завершения задачи с таймаутом, но это блокирует мой поток. Как я могу асинхронно ожидать завершения задачи с тайм-аутом?
22 ответа
Как насчет этого:
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
}
Вот версия метода расширения, которая включает в себя отмену тайм-аута, когда исходное задание завершается, как это было предложено Эндрю Арноттом в комментарии к его ответу.
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 (предварительная 7) или новее, для этого есть новый встроенный метод версияTask.WaitAsync .
// 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);
Ты можешь использовать Task.WaitAny
ждать первого из нескольких заданий.
Вы можете создать две дополнительные задачи (которые будут выполнены после указанного времени ожидания), а затем использовать WaitAny
ждать того, что завершится первым. Если задача, выполненная первой, является вашей "рабочей" задачей, то вы закончили. Если задача, выполненная первой, является задачей тайм-аута, вы можете отреагировать на тайм-аут (например, отмена запроса).
Это немного улучшенная версия предыдущих ответов.
- В дополнение к ответу Лоуренса он отменяет исходную задачу при истечении времени ожидания.
- В дополнение к вариантам ответа 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;
}
Используя превосходную библиотеку 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();
}
Вы можете использовать опцию Task.Wait, не блокируя основной поток, используя другую задачу.
Используйте таймер для обработки сообщения и автоматической отмены. Когда Задача завершится, вызовите 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;
}
С .Net 6 (предварительный просмотр 7 в качестве даты этого ответа) можно использовать новый WaitAsync (TimeSpan, CancellationToken), который отвечает этой конкретной потребности. Если вы можете использовать .Net6, эта версия, кроме того, описывается как оптимизированная по сравнению с большинством хороших решений, предлагаемых в этих сообщениях.
(Спасибо всем участникам, потому что я использовал ваше решение в течение многих лет)
Другой способ решения этой проблемы - использование 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;
Общая версия ответа @Kevan выше с Reactive Extensions.
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 == null
? task.ToObservable().Timeout(timeout).ToTask()
: task.ToObservable().Timeout(timeout, scheduler).ToTask();
}
КСТАТИ: Когда Тайм-аут происходит, исключение тайм-аута будет сгенерировано
Ради удовольствия я сделал для Task расширение OnTimeout. По истечении времени ожидания Task выполняет желаемое встроенное лямбда-действие Action() и возвращает 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 возвращает результат типа bool, который можно присвоить переменной, как в этом примере, вызывая асинхронный вызов UDP-сокета:
var t = UdpSocket.ReceiveAsync();
var timeout = await t.OnTimeout(task => {
Console.WriteLine("No Response");
}, 5000);
Переменная "задача" доступна в лямбда-выражении тайм-аута для дополнительной обработки.
Использование Action, принимающего объект, может вдохновить на создание различных других расширений.
Создайте расширение, чтобы дождаться завершения задачи или задержки, в зависимости от того, что наступит раньше. Выбрасывать исключение, если задержка выигрывает.
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;
}
Так что это древний вариант, но есть гораздо лучшее современное решение. Не уверен, какая версия 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 был введен более простой синтаксис для операторов using, но его достаточно легко переписать.
Я чувствовал Task.Delay()
задача и CancellationTokenSource
в другом ответе немного больше для моего варианта использования в тесном сетевом цикле.
И хотя метод Джо Хоага "Создание задачи".TimeoutAfter в блогах 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;
Несколько вариантов ответа Эндрю Арнотта:
Если вы хотите подождать существующую задачу и выяснить, завершена ли она или истекло время ожидания, но не хотите отменить ее, если истечет время ожидания:
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 } }
Если вы хотите запустить рабочую задачу и отменить ее, если истекло время ожидания:
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 }
Если у вас уже есть задача, которую вы хотите отменить в случае истечения времени ожидания:
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
Вот низкоуровневая реализация метода, который принимает как тайм-аут, так и
CancellationToken
, а в случае исключения распространяет все ошибки
Task<T>
, вместо первой ошибки:
public static Task<TResult> WaitAsync<TResult>(this Task<TResult> task,
TimeSpan timeout, CancellationToken cancellationToken = default)
{
if (task == null) throw new ArgumentNullException(nameof(task));
if (timeout < TimeSpan.Zero && timeout != Timeout.InfiniteTimeSpan)
throw new ArgumentOutOfRangeException(nameof(timeout));
var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
cts.CancelAfter(timeout);
return task
.ContinueWith(_ => { }, cts.Token,
TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default)
.ContinueWith(continuation =>
{
cts.Dispose();
if (task.IsCompleted) return task;
cancellationToken.ThrowIfCancellationRequested();
if (continuation.IsCanceled) throw new TimeoutException();
return task;
}, TaskScheduler.Default).Unwrap();
}
А
TimeoutException
выбрасывается, если тайм-аут истекает до завершения
task
.
Честно говоря, распространение всех ошибок на самом деле не является функцией добавления ценности в этом случае. Причина в том, что если вы используете вот так:
await someTask.WaitAsync(timeout)
, любые лишние ошибки будут поглощены
await
в любом случае, который по замыслу распространяет только первое исключение из ожидаемой задачи. И нет особого смысла хранить
WaitAsync
задачу в переменной и исследуем ее внутри
catch
блок, потому что у вас уже есть
someTask
доступен, и вы можете изучить это вместо этого.
Если вы используете BlockingCollection для планирования задачи, производитель может запустить потенциально долго выполняющуюся задачу, а потребитель может использовать метод 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);
}
}
На случай, если кто-то ищет что-то подобное (через 12 лет после вопроса ОП)..
Другой вариант — просто использовать Task.Wait(timeout) внутри другого Task.Run(). Это если вы хотите каким-то образом избежать использования Task.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
}
});
Определенно не делайте этого, но это вариант, если... Я не могу придумать вескую причину.
((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();