.NET 4.5 SslStream - Cancel a asynchronous read/write call? - c#

Is there any way to cancel a asynchronous read or write task on a SslStream? I have tried providing ReadAsync with a CancellationToken but it doesnt appear to work. When the following code reaches it's timeout (the Task.Delay), it calls cancel on the CancellationTokenSource which should cancel the read task, returns a error to the calling method, and the calling method eventually tries to read again, which raises a "The BeginRead method cannot be called when another write operation is pending" exception.
In my specific application I could work around this by closeing the socket and reconnecting, but there is a high overhead associated with reestablishing the connection so it is less than ideal.
private async Task<int> ReadAsync(byte[] buffer, int offset, int count, DateTime timeout)
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
if (socket.Poll(Convert.ToInt32(timeout.RemainingTimeout().TotalMilliseconds) * 1000, SelectMode.SelectRead) == true)
{
Task<int> readTask = stream.ReadAsync(buffer, offset, count, cancellationTokenSource.Token);
if (await Task.WhenAny(readTask, Task.Delay(timeout.RemainingTimeout())) == readTask)
return readTask.Result;
else
cancellationTokenSource.Cancel();
}
return -1;
}

Looking at the doc for SslStream, it does not support ReadAsync (it simply uses the fallback synchronous implementation from Stream). Since SslStream is a decorator Stream, is isn't obvious how to safely recover from a timeout on the underlying Stream, and the only obvious way would be to re-initialize the entire Stream pipeline. However given that the underlying stream might not be seekable, again this might not be idea.
For support of cancellation, the stream would have to override Stream.ReadAsync(Byte[], Int32, Int32, CancellationToken). In the documentation, neither NetworkStream nor SslStream overrides the overload of ReadAsync required to consume cancellation (and abstract Stream couldn't possibly implement generic cancellation). For an example where cancellation IS supported, see FileStream and contrast how the documentation differs.
So for a concrete case, if we were decorating HttpStream using SslStream then after a timeout we would want to recover by opening the HttpStream back at the position where we timed out (using the Range header). But there is no way to expose that generically using the IO.Stream class.
Finally you should consider what your failure case should be. Why would ReadAsync timeout? In the majority of cases I can think of, it should be due to unrecoverable network issues, which would necessitate the Stream being reinitialized.
Bonus point. Have you considered refactoring out your Timeout behaviour into a decorator Stream? You could then place the timeout decorator onto your underlying stream.
stream = new SslStream(
new TimeoutStream(new FooStream(), Timespan.FromMilliseconds(1000)));

Related

Why is NetworkStream.ReadAsync not timing out?

I am using NetworkStream with TcpClient.
First I setup my tcp client:
tcp = new TcpClient(AddressFamily.InterNetwork)
{ NoDelay = true, ReceiveTimeout = 5000};
My main data-receiving loop:
while (true)
{
//read available data from the device
int numBytesRead = await ReadAsync();
Console.WriteLine($"{numBytesRead} bytes read"); //BP2
}
And the actual TCP data reading:
public Task<int> ReadAsync()
{
var stream = tcp.GetStream();
return stream.ReadAsync(InBuffer, 0, InBuffer.Length); //BP1
}
I have this connected to a testbed which lets me send manual packets. Through setting breakpoints and debugging I have checked that stream.ReadTimeout takes the value 5000 from tcp.
If I send data frequently it all works as expected. But if I don't send any data, nothing appears to happen after 5s, no timeout. I see breakpoint BP1 being hit in the debugger but until I send data from my testbed, BP2 is not hit. I can leave it a minute or more and it just seems to sit waiting, but receives data sent after a minute, which appears to be incorrect behavior. After 5 seconds something should happen, surely (an exception as I understand it)?
It's late so I am expecting something really basic but can anyone see what my mistake is and a resolution?
Addendum
OK so when I RTFM for the actual .Net version I'm using (how may times have I been caught out by MS defaulting to .Net Core 3, I did say it was late) I see in the remarks sectio for ReadTimeout:
This property affects only synchronous reads performed by calling the
Read method. This property does not affect asynchronous reads
performed by calling the BeginRead method.
I'm unclear now if I can use modern awaitable calls at all to read socket data safely and with a timeout specifically. It's working except for the timeout but I'm not sure how given ReadAsync has no override in NetworkStream. Must I do some ugly hack or is there a simple solution?
In my case 5000 is the longest I can expect not to receive data before concluding there is a problem - the protocol has no ping mechanism so if nothing appears I assume the connection is dead. Hence thinking an Async read with a 5000ms timeout would be nice and neat.
Timeout values for network objects apply only to synchronous operations. For example, from the documentation:
This option applies to synchronous Receive calls only.
For Socket.ReceiveTimeout, TcpClient.ReceiveTimeout, and NetworkStream.ReadTimeout, the implementations all ultimately result in a call to SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, ...) which in turn is effectively calling the native setsockopt() function. From that documentation:
SO_RCVTIMEO DWORD Sets the timeout, in milliseconds, for blocking receive calls.
(emphasis mine)
It's this limitation in the underlying native API that is the reason for the same limitation in the managed API. Timeout values will not apply to asynchronous I/O on the network objects.
You will need to implement the timeout yourself, by closing the socket if and when the timeout should occur. For example:
async Task<int> ReadAsync(TcpClient client, byte[] buffer, int index, int length, TimeSpan timeout)
{
Task<int> result = client.GetStream().ReadAsync(buffer, index, length);
await Task.WhenAny(result, Task.Delay(timeout));
if (!result.IsCompleted)
{
client.Close();
}
return await result;
}
Other variations on this theme can be found in other related questions:
NetworkStream.ReadAsync with a cancellation token never cancels
Cancel C# 4.5 TcpClient ReadAsync by timeout
Closing the socket is really all that you can do. Even for synchronous operations, if a timeout occurs the socket would no longer be usable. There is no reliable way to interrupt a read operation and expect the socket to remain consistent.
Of course, you do have the option of prompting the user before closing the socket. However, if you were to do that, you would implement the timeout at a higher level in your application architecture, such that the I/O operations themselves have no awareness of timeouts at all.

Task.Factory.FromAsync with CancellationTokenSource

I have the following line of code used to read asynchronously from a NetworkStream:
int bytesRead = await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null);
I'd like to make it support cancellation. I see that I can cancel tasks using a CancellationTokenSource, however I don't see any way I can pass it to TaskFactory.FromAsync().
Is it possible to make a FromAsync()-constructed task support cancellation?
Edit: I want to cancel a task that is already running.
Gigi, unfortunately the semantic nature of FromAsync indicates that you are only adapting an asynchronous process to TPL's API (TPL = Microsoft's Task Parallel Library)
In essence, TPL's ReadAsync controls the async behaviour itself, whilst FromAsync only wraps the behaviour (but doesn't control it).
Now since Cancellation is a TPL specific construct, and since FromAsync has no control on the inner workings of the async method being called, then there is no guaranteed way to cleanly cancel the task and ensure that all resources are closed correctly (which is why it was omitted. If you're curious, just decompile the method ;))
In these situations, it makes more sense to wrap the actual async call yourself in a normal task and detect the OperationCancelled exception, which will give you the opportunity to close your stream by making the appropriate calls.
In short, the answer is no, but there is nothing stopping you from creating a generic overloaded method that will pick the correct strategy to cleanly close a stream depending on its type.
As others have already mentioned, there is no clean way of achieving what you're asking for. The notion of cancellation was absent from the Asynchronous Programming Model; thus, it couldn't be retrofitted through the FromAsync converters.
However, you can introduce cancellation for the Task that wraps the asynchronous operation. This will not cancel the underlying operation itself – your NetworkStream would still continue reading all the requested bytes from the socket – but it will permit your application to react as if the operation was cancelled, immediately throwing an OperationCanceledException from your await (and executing any registered task continuations). The result of the underlying operation, once completed, will be ignored.
This is a helper extension method:
public static class TaskExtensions
{
public async static Task<TResult> HandleCancellation<TResult>(
this Task<TResult> asyncTask,
CancellationToken cancellationToken)
{
// Create another task that completes as soon as cancellation is requested.
// http://stackoverflow.com/a/18672893/1149773
var tcs = new TaskCompletionSource<TResult>();
cancellationToken.Register(() =>
tcs.TrySetCanceled(), useSynchronizationContext: false);
var cancellationTask = tcs.Task;
// Create a task that completes when either the async operation completes,
// or cancellation is requested.
var readyTask = await Task.WhenAny(asyncTask, cancellationTask);
// In case of cancellation, register a continuation to observe any unhandled
// exceptions from the asynchronous operation (once it completes).
// In .NET 4.0, unobserved task exceptions would terminate the process.
if (readyTask == cancellationTask)
asyncTask.ContinueWith(_ => asyncTask.Exception,
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
return await readyTask;
}
}
And this is an example that uses the extension method to treat an operation as cancelled after 300ms:
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(300));
try
{
int bytesRead =
await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null)
.HandleCancellation(cts.Token);
}
catch (OperationCanceledException)
{
// Operation took longer than 300ms, and was treated as cancelled.
}
No, there is no generic way to cancel such a task. Cancellation is API specific.
For example, WebClient has a Cancel method.
A Socket or a FileStream needs to be Close'd to cancel an outstanding call.
Web-service clients have even different ways of aborting calls.
...
This is because the implementer of the IO operation must support cancellation.
It might seem tempting to use NetworkStream.ReadAsync and pass a cancellation token but is Stream.ReadAsync. An the latter just throws away the token. Basically not supported.
Stream.ReadAsync is just the base class method. It does not do anything by itself. Concrete IO operations are issued only by derived classed. Those must support cancellation natively. Stream can't do anything to force them. It happens that NetworkStream doesn't support cancellation.
I understand that you want to cancel the operation and leave the socket open. But it is not possible. (Subjective note: This is really a sad state of affairs. Especially considering that Windows supports cancellable IO at the Win32 level.)
If you still want your app to quickly continue, although the IO operation is not cancellable, just ignore the result of that task and continue. Be aware that eventually the IO might complete and for example drain data from the socket buffers or cause other side-effects.
"Cancelling by ignoring" effectively make the stream position undefined. The stream becomes unusable. This doesn't really avoid the need to open a new stream. You still have to get rid of the old stream (in most cases) and reopen. Also, you are introducing concurrency.

Sequential asynchronous reads using Stream.BeginRead

I'm writing a class that exposes a subsection of a stream for reading. Since data may be read from multiple different subsections at the same time, only one operation may be active at any one time.
I had the idea of locking the underlying stream before every operation. Is locking the stream around the BeginRead call sufficient to ensure that concurrent asynchronous reads from different positions in the underlying stream happen correctly?
public sealed class SubStream : Stream
{
// ...
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count,
AsyncCallback callback, object state)
{
lock (this.baseStream)
{
this.baseStream.Seek(this.offset + this.position, SeekOrigin.Begin);
return this.baseStream.BeginRead(buffer, offset, count,
callback, state);
}
}
public override int EndRead(IAsyncResult asyncResult)
{
int read;
lock (this.baseStream)
{
read = baseStream.EndRead(asyncResult);
this.position += read;
}
return read;
}
// Read() and ReadByte() also lock on this.baseStream (not shown).
// ...
}
For example, if thread A calls BeginRead, a lock on the base stream is acquired. Now thread B calls BeginRead and has to wait for the lock to be released. Thread A sets the position of the base stream and starts an asynchronous read operation. Then releases the lock. Thread B then acquires the lock and changes the position of the base stream and starts another asynchronous read operation. Then, sometime later, the asynchronous read from thread A completes. Can I be sure that this reads from the original position in the base stream? If not, how do I fix it?
Here you might end up with multiple threads calling BeginRead on the same instance of resource (baseStream). As per MSDN, the "EndRead must be called exactly once for every call to BeginRead. Failing to end a read process before beginning another read can cause undesirable behavior such as deadlock." In you case, I recon a trouble 'if Thread B is on Seek (on baseStream) while Thread A in the middle of executing their EndRead(callback)'.
Due to the nature of requirement, you are better off with wrapping multi-threaded access with synchronous I/O. This means, the current implementation can be amended with synchronous I/O instead of asynchronous I/O. Also, you may want to consider informing queuing threads about the completion of former threads using Monitor.WaitOne (baseStream) and Monitor.Pulse(baseStream) or Monitor.PulseAll(baseStream).
Alternately, I would like to throw another idea of Memory-Mapped file for segmented style.
In the given code snippet you'll read multiple times from the same position. Move the position update to the BeginRead function. Apart from that you are honoring the contract of the FileStream class by never concurrently calling its methods.

When does a Stream EndRead Block the Callback

I am relatively new to using the Async pattern for stream reading and writing, and wondered if the answer to this question is so obvious it is not written anywhere explicitly:
When calling a NetworkStream.BeginRead, I pass a callback parameter, which according to MSDN is executed "when BeginRead completes". It also says that "your callback method should call the EndRead method."
Then according to the documentation for NetworkStream.EndRead, the "method completes the asynchronous read operation started in the BeginRead method." It also mentions that this method "blocks until data is available."
I know the EndRead method is also useful for determining the number of bytes received.
My question is:
If the EndRead method is called within the BeginRead callback, does it ever really block? Isn't the read operation already complete when the callback is invoked?
Sample Code
byte[] streamBuffer = new byte[1024];
public void SomeFunction()
{
TcpClient client = new TcpClient();
client.Connect(IPAddress.Parse("127.0.0.1"), 32000);
NetworkStream stream = client.GetStream();
stream.BeginRead(streamBuffer,0,streamBuffer.Length,ReadCallback,stream);
}
public void ReadCallback(IAsyncResult ar)
{
NetworkStream stream = ar.AsyncState as NetworkStream;
// Will this call ever actually "block" or will it
// return immediately? Isn't the read operation
// already complete?
int bytesRead = stream.EndRead(ar);
// Other stuff here
}
The read operation is always completed when the callback fires. Completion is what gets the callback fired in the first place. So EndRead() will never block when it is used in the callback method.
Do note that "completed" can also mean "failed", EndRead() will throw the exception. A very common exception is ObjectDisposedException, thrown when the socket is closed while an asynchronous read is in progress. Typical when you exit your program, be sure to catch it.
You can use EndRead in two situations:
You can use EndRead in your call back functions. At this point nothing will be blocked.
You can also use EndRead without the call back function:
myStream.BeginRead(...);
// Do a lot of work at the same time.
// When my work is done, wait for the stream to finish its work.
myStream.EndRead(...);
EndRead should always be called to report some errors have occured. If an error has occured, an EndRead will throw an exception.
No, because the EndRead method is being called (and blocking) within the delegate which is being called asynchronously. So yes, the method with EndRead is blocking, but not on the thread of execution that called BeginRead.

How to create C# Compact Framework non-blocking sockets

I am trying to create a non-blocking socket in WM6 C# but I keep getting the following compiler error:
"Synchronous calls are not supported on non-blocking sockets. Set Blocking=true or use the asynchronous methods."
However when I use the BeginReceive() asynchronous methods, the EndReceive() is blocked. Same for BeginSend() and EndSend(). How do you do a non-blocking socket within the compact framework?
Here's my code. I am not using an AsyncCallback method because I want to return the bytesRecv and bytesSent variables.
private void asyncReceive(byte[] recvBytes, int offset, int size, SocketFlags sf)
{
IAsyncResult asyncResult = null;
int recvBytes = 0;
try
{
asyncResult = _socket.BeginSend(sendBytes, offset, size, sf, null, null);
recvBytes = _socket.EndSend(asyncResult); // <-- This blocks even with _socket.Blocking = false;
}
catch (SocketException)
{
//Check SocketException.ErrorCode...
}
return recvBytes;
}
Looks like you're missing the point- the behavior you're seeing is by design. I'd suggest reading up on async programming in .NET (lots of resources- maybe start here). Non-blocking socket reads/writes with BeginXXX will allow you to start the send/receive, then go do something else and be notified by the kernel when it's done (via the AsyncCallback), at which point you'd call the corresponding EndXXX method to get the result. If you call EndXXX right after the BeginXXX call before the operation is complete, it's designed to block until completion.
To get non-blocking behavior, you'll need to break up your code a bit to handle the callback correctly (eg, marshal the result back to your UI, whatever). You won't have the answer as to how many bytes were sent/received until that's actually been done by the underlying kernel bits.

Categories

Resources