получаю данный от Tcp сервера, если запрос длится более 1 сек, то завершаем Task
ожидания получения ответа и выкидываем TimeoutException("....")
.
Также если прилетит CancellationToken
, то сразу все отключаем и выкидываем OperationCanceledException("....")
.
В разных проектах замечал разные реализации такого функционала, но все они какие-то мутные, помогите разобраться
1. Самый частый вариант
public static T WithTimeout<T>(this Task<T> task, int time, CancellationToken ct)
{
var isCompletedSuccessfully = task.Wait(time, ct);
if (isCompletedSuccessfully)
{
return task.Result;
}
throw new TimeoutException("The function has taken longer than the maximum time allowed.");
}
//Но тут task мы не отключаем, тоесть будем плодить висячие таски.
//task.Wait() синхронное ожидание заврешения, что тоже неверно.
2. Тоже в паре проектов используется.
public static async Task<T> WithTimeout<T>(Task<T> task, int time, CancellationToken ct)
{
Task delayTask = Task.Delay(time, ct);
Task firstToFinish = await Task.WhenAny(task, delayTask);
if (firstToFinish == delayTask)
{
task.ContinueWith(HandleException, ct); //к основной задаче прикрепили обработку иключений
throw new TimeoutException();
}
return await task;
}
private static void HandleException<T>(Task<T> task)
{
if (task.Exception != null)
{
; //чтото делаем с исключеним возникшим в основной задаче.
}
}
//task также не отключаем а подписываемся на резульат выполнения и все таки ЖДЕМ.
А если просто использовать CancelAfter? Ну да из минусов постоянно нужно создать CancellationTokenSource перед вызовом метода и связывать им таски. Но вроде он работает правильно?
public static async Task<T> WithTimeout<T>(this Task<T> task, int time, CancellationTokenSource ctsTask)
{
ctsTask.CancelAfter(time);
try
{
return await task;
}
catch (OperationCanceledException ex)
{
throw new TimeoutException("The function has taken longer than the maximum time allowed.");
}
}
**ИСПОЛЬЗОВАНИЕ:**
public async Task<byte[]> TakeDataAsync(int nbytes, int timeOut, CancellationToken ct)
{
byte[] bDataTemp = new byte[256];
var ctsTimeout = new CancellationTokenSource();//токен сработает по таймауту в функции WithTimeout
var cts = CancellationTokenSource.CreateLinkedTokenSource(ctsTimeout.Token, ct); // Объединенный токен, сработает от выставленного ctsTimeout.Token или от ct
int nByteTake = await _terminalNetStream.ReadAsync(bDataTemp, 0, nbytes, cts.Token).WithTimeout(timeOut, ctsTimeout);
if (nByteTake == nbytes)
{
var bData = new byte[nByteTake];
Array.Copy(bDataTemp, bData, nByteTake);
return bData;
}
return null;
}
Т.е. Связываем таски через CancellationTokenSource. и Просто отменяем задачу по времени. Чтобы отменить задачу по любому из токенов использую CancellationTokenSource.CreateLinkedTokenSource()
еще не понятно как отличать завершилась задача по времени или по ОТМЕНЕ, т.к. cts общий. И еще нужен самый быстрый вариант по перфомансу, т.к. используется клиент на RasberiPi под AspNetCore.
Нужно ли уничтожать CancellationTokenSource через Dispose после отработки функции?
Была точно такая же задача. Вот результаты моих изысканий.
Впрочем, самое важное то, что у вас специфическая задача.
Дело в том, что NetworkStream.ReadAsync игнорирует CancellationToken (но принимает. фича:) ) и если вы ожидаете, что при отмене он кинет исключение при срабатывании CancelAfter(), то этого не будет.
Поэтому при таймауте нужно закрыть сокет, чтобы ReadAsync() отвалился. При этом вылетит совсем не OperationCanceledException и приходится это детектить. Вот вам для примера псевдокод:
public async Task<...> RequestAsync(..., TimeSpan timeout, CancellationToken ct)
{
using (var tcpClient = new TcpClient())
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
try
{
cts.CancelAfter(timeout);
try
{
using (ct.Register(tcpClient.Close))
{
//...
}
}
catch (ObjectDisposedException)
{
ct.ThrowIfCancellationRequested();
throw;
}
catch (IOException ioException)
{
ct.ThrowIfCancellationRequested();
throw new ...("error", ioException);
}
catch (SocketException socketException)
{
ct.ThrowIfCancellationRequested();
throw new ...("error", socketException);
}
}
catch (OperationCanceledException) when (cts.IsCancellationRequested)
{
//тут "наш" OperationCanceledException
}
}
}
Также можно ловить все OperationCanceledException и смотреть на токены на случай если вдруг кто-то еще может бросить токен отмены (например, HttpClient так делает) если нужно разное поведение. Ну или сделать несколько catch..when
Остается вопрос: "а если я не хочу закрывать сокет при таймауте". На этот вопрос я ответа не знаю.
update вернее знаю вот такой вариант Но, насколько я понимаю, мы так получаем повисшую на ReadAsync таску и сокет в неопределенном состоянии, с которым непонятно что делать.
Вот короче вариант если воспользоваться WithCancellation из ссылки
private async Task<...> MakeRequestAsync(..., TimeSpan timeout, CancellationToken ct)
{
using (var tcpClient = new TcpClient())
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
cts.CancelAfter(timeout);
Task op = _stream.ReadAsync(..., cts.Token)
try
{
await op.WithCancellation(cts.Token);
}
catch (OperationCanceledException)
{
//у нас повис ReadAsync который выбросит исключение после закрытия сокета, а значит нужно их погасить
if (!op.IsCompleted)
op.ContinueWith(t => /* handle eventual completion */);
//при этом исключения IO/Socket*Exception не будут пойманы
}
}
}
Виртуальный выделенный сервер (VDS) становится отличным выбором
Как сделать что бы из textbox значения перевести в тип double и что бы читалась в числе и точка и запятая?И как вывести в label тип double?
Зашел в тупик с десериализацией Json, подскажите, почему?