I get the data from the TCP server, if the request lasts more than 1 second, then we finish the Task while waiting for the response and throw out a TimeoutException("....") . Also, if CancellationToken arrives, then immediately disable everything and throw away OperationCanceledException("....") .

In different projects I noticed different implementations of such functionality, but they are all muddy, help me figure it out.

1. The most frequent option

 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. Also used in a couple of projects.

  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 также не отключаем а подписываемся на резульат выполнения и все таки ЖДЕМ. 

And if you just use CancelAfter? Well, yes of the minuses constantly need to create a CancellationTokenSource before calling the method and associate task with it. But he seems to be working properly?

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

Those. We connect tasks through CancellationTokenSource. And just cancel the task on time. To cancel a task for any of the tokens using CancellationTokenSource.CreateLinkedTokenSource ()

it is still not clear how to distinguish the completed task by time or by CANCEL, since cts is common. And you also need the fastest version of performance, because client is used on RasberiPi under AspNetCore.

Do I need to kill the CancellationTokenSource through Dispose after working out the function?

    1 answer 1

    There was exactly the same task. Here are the results of my research.

    1. CancellationTokenSource when using CancelAfter is better to disposal, because it will cancel the internal timer and the triggering of possible callbacks (which were added via Register ()) when it is not needed.
    2. To distinguish, OperationCanceledException looks at the state of tokens. If OperationCanceledException took off and the client token was canceled, then the client canceled, otherwise something else was canceled (our CancellationTokenSource)
    3. The WithTimeout variant with CancelAfter () in your case is just a wrapper for creating the necessary exception. And since it cannot parse whose cancellation it was, it is necessary to transfer the creation of the CancellationTokenSource inside WithTimeout and transfer the client token for comparison "who canceled".
    4. The implementation of WithTimeout with Task.Delay is wrong, because the forgotten task continues to work, and you need to cancel it, so the method itself should have its own CancellationTokenSource in general

    However, the most important thing is that you have a specific task.

    The fact is that NetworkStream.ReadAsync ignores CancellationToken (but accepts. Feature :)) and if you expect that when canceled it throws an exception when CancelAfter () is triggered, then this will not happen.

    Therefore, when timeout, you need to close the socket so that ReadAsync () falls off. In this case, an OperationCanceledException will crash at all and you have to detect it. Here's an example of pseudocode:

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

    You can also catch all the OperationCanceledException and look at the tokens in case someone else might throw a cancel token (for example, HttpClient does this) if you need different behavior. Well, or make a few catch..when

    The question remains: "and if I do not want to close the socket on timeout." I do not know the answer to this question.

    update I know this option more correctly But, as far as I understand, we get the Task and the socket hanging on ReadAsync in an uncertain state, with which it is not clear what to do.

    Here is a shorter option if you use WithCancellation from the link

      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 не будут пойманы } } } 
    • NetworkStream.ReadAsync exactly cancel does not work, If only to transfer a token that has already been triggered, only then there is a cancel, I noticed that I didn’t want to close the socket, and I really don’t want every timeout. - Aldmi
    • I asked the question to be generalized, for NORMALLY completing tasks I ran into a specific problem)) this is true - Aldmi
    • I added an option where we do not close the socket to interrupt ReadAsync, but we have the read task and the socket in an undefined state. After all, the data of this task may eventually come to, and may not be. And if we re-read from the same socket, we will have garbage. imho - vitidev
    • Thank! look at your option. Judging by the logs of the system, the TcpIp transport timeout is extremely rare, if the server is disconnected, then immediately the data is sent and the client goes into the cyclic state of the reconnect. Those. when there is a connection, it takes time for the connection to arrive while waiting, so I didn’t encounter a problem with the stuck task of reading the response. You can, of course, put all tasks of waiting for a response to the list in the forehead, and when the incomplete tasks become a lot, then reconnect the socket. But these are crutches in taste and color)) - Aldmi
    • @Aldmi no. The hung task waits for data reading. And if the data for the second request comes later on the same socket, then in theory this task subtracts them and we will be late with our new await ReadAsync. Or partially subtracts and then generally can not imagine what will happen. Therefore, if the task hangs = the socket is corrupted. Maybe not, but I did not dig so deep. - vitidev