How to stop reading from stream on a disconnect gracefully? - c#

I have a simple socket connection that runs a read task in async. When I disconnect I get an exception:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'SslStream'.
I don't know how to stop it trying to read when it is sitting and waiting to read a message when I call disconnect. This is my read code:
public async Task<int> Read(byte[] readBuffer)
{
if (!IsConnected)
{
return 0;
}
try
{
await Stream.ReadAsync(_header, 0, _header.Length);
return await Stream.ReadAsync(readBuffer, 0, _header[0]);
}
catch (Exception e)
{
Debug.Log(e);
Disconnect();
return 0;
}
}
protected async Task _RunReader()
{
var totalBytes = await Read(_readBuffer);
if (totalBytes > 0)
{
HandleReader(_readBuffer, totalBytes);
_RunReader();
}
}
And this is my disconnect code:
public async Task Disconnect(bool graceful = false)
{
if (IsConnected && graceful)
{
lockPackets = true;
Empty[0] = 1; // send disconnect message to server for graceful
await Send(Empty,1);
}
Stream?.Close();
TcpClient.Close();
IsConnected = false;
}
Is there any way to abandon ReadAsync when Disconnect is called to avoid getting an exception? It doesn't effect the program since I try catch, but I feel like there's probably a more elegant way than just allowing it throw silent exceptions?

Related

ClientWebSocket.ConnectAsync closing immediately

I'm implementing a small ClientWebSocket to send messages to the IPAddress.Loopback address of the computer so that a browser utility page can pick them up. however as soon as I connect, the client is immediately closed without message, reason, or exception.
ClientWebSocket cws;
Uri address = new Uri("ws://" + IPAddress.Loopback.ToString() + ":80/"
async void Start(){
ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls |
SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
using (cws = new ClientWebSocket()) {
cws.Options.KeepAliveInterval = new TimeSpan(1, 0, 0);
//Start response listener loop
_ = Task.Run(() => async_ListenerLoop(receiveCancelTokenSrc.Token).ConfigureAwait(false));
try {
//Start the connection
cws.ConnectAsync(address, sendCancelTokenSrc.Token);
} catch(Exception ex) {
MessageBox.Show("Unable to connect.\n\r\n\r" + ex.Message);
}
}
}
async Task async_ListenerLoop(CancellationToken cancelToken) {
ArraySegment<byte> buffer = WebSocket.CreateServerBuffer(RECIEVE_BUFFER);
List<byte> rawData = new List<byte>();
try {
while (state != WebSocketState.Closed && state != WebSocketState.Aborted && !cancelToken.IsCancellationRequested) {
if(state == WebSocketState.Connecting) {
//Sometimes it waits 2-3 cycles, other times it closes immediately
await Task.Delay(500);
continue;
}
WebSocketReceiveResult receiveResult = await cws.ReceiveAsync(buffer, cancelToken);
if (!cancelToken.IsCancellationRequested) {
if (state == WebSocketState.CloseReceived && receiveResult.MessageType == WebSocketMessageType.Close) {
sendCancelTokenSrc.Cancel();
await cws.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "Acknowledge Close frame", CancellationToken.None);
//The socket state changes to closed at this point
}
if (state == WebSocketState.Open) {
if (receiveResult.MessageType == WebSocketMessageType.Text) {
rawData.AddRange(buffer.Array);
if (receiveResult.EndOfMessage) {
string strData = Encoding.UTF8.GetString(rawData.ToArray(), 0, rawData.Count);
ProcessData(JsonConvert.DeserializeObject<RequestDigest>(strData));
rawData.Clear();
}
} else if (receiveResult.MessageType == WebSocketMessageType.Binary) {
rawData.AddRange(buffer.Array);
if (receiveResult.EndOfMessage) {
ProcessData(rawData.ToArray());
rawData.Clear();
}
}
}
}
}
//Descripion is always null
MessageBox.Show("Connection was closed because '"+cws.CloseStatusDescription+"'");
} catch (OperationCanceledException ex) {
// normal upon task/token cancellation, disregard
MessageBox.Show("Connection to server cancelled.\n\r\n\r" + ex.Message);
} catch (JsonReaderException ex) {
MessageBox.Show("Unable to read incoming message.\n\r\n\r" + ex.Message);
} catch (Exception ex) {
MessageBox.Show("Unable recieve incoming message.\n\r\n\r" + ex.Message);
} finally {
sendCancelTokenSrc.Cancel();
AbortConnection();
}
}
void AbortConnection() {
// don't leave the socket in any potentially connected state
if (state != WebSocketState.Closed) {
cws.Abort();
}
cws.Dispose();
}
There may be other problems with your code (wiring up sockets and clients takes a bit of messing about before you get it right) but there are two glaring problems:
You need to await the result of ConnectAsync(...), to stop the code of Start() continuing synchronously without it:
await cws.ConnectAsync(address, sendCancelTokenSrc.Token);
And you need to make sure that cws does not get disposed until you've finished with it. The using(cws) { ... } block will dispose cws as soon as the code leaves the squiggly braces (which happens straight away currently), even though your listener is still running, and needs it to remain alive.
You can either do that by keeping the code within the using block by awaiting an asynchronous result (such as hint! a final result from your listener) - or by not using using at all and manually disposing cws yourself at the right time.
Also make sure to only start your listener after the connection call has completed, to ensure cws is in a good state.
Something like this:
using(cws = new ClientWebSocket())
{
await cws.ConnectAsync(...);
await DoSomeListeningForABit();
} //cws gets implicitly disposed here
And... your listener will doubtlessly require more work too, but the above will get you past your immediate problem (hopefully!)

Async Task exception not being caught in Try-Catch

I am trying to cancel a Socket.ReceiveAsync() call by calling Socket.Close() and handling the inevitable SocketException.
My problem is that the exception never seems to make it to the try/catch in the calling function. Like this:
public async Task DoSomethingAsync()
{
Try
{
while(true)
{
await DoSomethingMoreSpecificAsync();
....
}
}
catch(Exception ex)
{
do some logging... <-- exception never makes it here
}
finally
{
cleanup stuff(); <-- or here...
}
}
public async Task DoSomethingMoreSpecificAsync()
{
var read = await _sock.ReceiveAsync(...); <-- Exception is thrown here
}
So when the exception is thrown, the application never makes it to the catch or finally blocks. It just complains of an unhandled exception. AFAIK async Task methods should propagate their exceptions upwards like any other method. I know that this would be normal if I was using async void...
In the console, after the app has crashed, I see this:
terminate called after throwing an instance of 'PAL_SEHException'
Here is the actual code:
private async Task DoReceiveAsync()
{
var writer = _inputPipe.Writer;
Exception ex = null;
try
{
while (true)
{
var mem = writer.GetMemory(TLS_MAX_RECORD_SIZE);
MemoryMarshal.TryGetArray(mem, out ArraySegment<byte> buf);
//Since OpenSSL will only gve us a complete record, we need a func that will piece one back together from the stream
await ReadRecord(mem);
var res = _ssl.Read(buf.Array, TLS_MAX_RECORD_SIZE);
writer.Advance(res);
var flush = await writer.FlushAsync();
if (flush.IsCanceled || flush.IsCompleted)
break;
}
}
catch (Exception e) { ex = e; }
finally { writer.Complete(ex); }
}
private async Task ReadRecord(Memory<byte> mem)
{
MemoryMarshal.TryGetArray(mem, out ArraySegment<byte> buf);
SslError res;
do
{
var sockRead = await _socket.ReceiveAsync(buf, SocketFlags.None);
if (sockRead == 0) break;
_ssl.ReadBio.Write(buf.Array, sockRead);
var sslRead = _ssl.Peek(peekBuf, TLS_MAX_RECORD_SIZE);
res = _ssl.GetError(sslRead);
} while (res != SslError.SSL_ERROR_NONE);
return;
}

How can I gracefully terminate a BLOCKED thread?

There are plenty of places that deal with terminating C# threads gracefully. However, they rely on a loop or if condition executing inside a loop, which assumes that this statement will be executed frequently; thus, when the stop bool flag is set, the thread exits quickly.
What if I have a thread in which this is not true? In my case, this is a thread set up to receive from a server, which frequently blocks on a call to read data from the input stream, where none is yet provided so it waits.
Here is the thread in question's loop:
while (true)
{
if (EndThread || Commands.EndRcvThread)
{
Console.WriteLine("Ending thread.");
return;
}
data = "";
received = new byte[4096];
int bytesRead = 0;
try
{
bytesRead = stream.Read(received, 0, 4096);
}
catch (Exception e)
{
Output.Message(ConsoleColor.DarkRed, "Could not get a response from the server.");
if (e.GetType() == Type.GetType("System.IO.IOException"))
{
Output.Message(ConsoleColor.DarkRed, "It is likely that the server has shut down.");
}
}
if (bytesRead == 0)
{
break;
}
int endIndex = received.Length - 1;
while (endIndex >= 0 && received[endIndex] == 0)
{
endIndex--;
}
byte[] finalMessage = new byte[endIndex + 1];
Array.Copy(received, 0, finalMessage, 0, endIndex + 1);
data = Encoding.ASCII.GetString(finalMessage);
try
{
ProcessMessage(data);
}
catch (Exception e)
{
Output.Message(ConsoleColor.DarkRed, "Could not process the server's response (" + data + "): " + e.Message);
}
}
The if statement at the top of the block does what the usual stopping-a-thread-gracefully setup does: checks a flag, terminates the thread if it's set. However, this thread is usually to be found waiting a few lines further down, at stream.Read.
Given this, is there any way to gracefully terminate this thread (i.e. no Aborting), and clean up its resources (there's a client that needs to be closed)?
Assuming you can use async / Tasks, the way to do clean stopping of async and IO operations is with a CancelationToken that is connected to a CancelationTokenSource. The below code snippet illustrates a simplified example of its usage when applied to a simplified version of your code.
class MyNetworkThingy
{
public async Task ReceiveAndProcessStuffUntilCancelled(Stream stream, CancellationToken token)
{
var received = new byte[4096];
while (!token.IsCancellationRequested)
{
try
{
var bytesRead = await stream.ReadAsync(received, 0, 4096, token);
if (bytesRead == 0 || !DoMessageProcessing(received, bytesRead))
break; // done.
}
catch (OperationCanceledException)
{
break; // operation was canceled.
}
catch (Exception e)
{
// report error & decide if you want to give up or retry.
}
}
}
private bool DoMessageProcessing(byte[] buffer, int nBytes)
{
try
{
// Your processing code.
// You could also make this async in case it does any I/O.
return true;
}
catch (Exception e)
{
// report error, and decide what to do.
// return false if the task should not
// continue.
return false;
}
}
}
class Program
{
public static void Main(params string[] args)
{
using (var cancelSource = new CancellationTokenSource())
using (var myStream = /* create the stream */)
{
var receive = new MyNetworkThingy().ReceiveAndProcessStuffUntilCancelled(myStream, cancelSource.Token);
Console.WriteLine("Press <ENTER> to stop");
Console.ReadLine();
cancelSource.Cancel();
receive.Wait();
}
}
}
.

Proper way to handle a continuous TCPClient ReadAsync Networkstream exception

I have an application that runs a background thread which communicates to multiple devices. The devices send me data based off an external trigger that I do not control. I have to wait for the devices to send me data and operate on them. If an exception occurs, I need to display that on the UI.
I am attempting to continuously read data over the network stream. When data comes, I need to raise it as an event and then start reading again. I need to be able to handle if an exception is thrown such as the device being disconnected.
At the base I have a network stream reading asynchronously
public Task<string> ReadLinesAsync(CancellationToken token)
{
_readBuffer = new byte[c_readBufferSize];
// start asynchronously reading data
Task<int> streamTask = _networkstream.ReadAsync(_readBuffer, 0, c_readBufferSize, token);
// wait for data to arrive
Task<String> resultTask = streamTask.ContinueWith<String>(antecedent =>
{
// resize the result to the size of the data that was returned
Array.Resize(ref _readBuffer, streamTask.Result);
// convert returned data to string
var result = Encoding.ASCII.GetString(_readBuffer);
return result; // return read string
}, token);
return resultTask;
}
So the way I am trying to do this is on start up, I start a thread that is reading by running the Start() method. But when an exception is thrown it kills my program even though I put some error trapping around it. I have been attempting to trap it at different places just to see what happens but I have not been able to figure out the proper way to do this so that I can raise the error to the UI without bombing out my application.
async public override void Start()
{
try
{
await _client.ReadLinesAsync(_cts.Token).ContinueWith(ReadLinesOnContinuationAction, _cts.Token);
}
catch (AggregateException ae)
{
ae.Handle((exc) =>
{
if (exc is TaskCanceledException)
{
_log.Info(Name + " - Start() - AggregateException"
+ " - OperationCanceledException Handled.");
return true;
}
else
{
_log.Error(Name + " - Start() - AggregateException - Unhandled Exception"
+ exc.Message, ae);
return false;
}
});
}
catch (Exception ex)
{
_log.Error(Name + " - Start() - unhandled exception.", ex);
}
}
async private void ReadLinesOnContinuationAction(Task<String> text)
{
try
{
DataHasBeenReceived = true;
IsConnected = true;
_readLines.Append(text.Result);
if (OnLineRead != null) OnLineRead(Name, _readLines.ToString());
_readLines.Clear();
await _client.ReadLinesAsync(_cts.Token).ContinueWith(ReadLinesOnContinuationAction, _cts.Token);
}
catch (Exception)
{
_log.Error(Name + " - ReadLinesOnContinuationAction()");
}
}
The debugger usually stops on the line:
_readLines.Append(text.Result);
I tried putting this within a check for the text.IsFaulted flag but then I bombed out on the .ContinueWith.
Does anyone have any ideas on what I need to fix this so that I can properly catch the error and raise the even to the UI? This code has all kinds of bad smells to it but I am learning this as I go along. Thanks for any help you can give.
Bombed out with which ContinueWith? You have at least two that I see.
Personally, I would not mix async/await with ContinueWith. The await is already giving you a convenient way to do a ContinueWith, so just use that. For example:
public async Task<string> ReadLinesAsync(CancellationToken token)
{
_readBuffer = new byte[c_readBufferSize];
// start asynchronously reading data
int readResult = await _networkstream.ReadAsync(_readBuffer, 0, c_readBufferSize, token);
// after data arrives...
// resize the result to the size of the data that was returned
Array.Resize(ref _readBuffer, streamTask.Result);
// convert returned data to string
return Encoding.ASCII.GetString(_readBuffer);
}
async public override void Start()
{
try
{
string text = await _client.ReadLinesAsync(_cts.Token);
await ReadLinesOnContinuationAction(text);
}
catch (AggregateException ae)
{
ae.Handle((exc) =>
{
if (exc is TaskCanceledException)
{
_log.Info(Name + " - Start() - AggregateException"
+ " - OperationCanceledException Handled.");
return true;
}
else
{
_log.Error(Name + " - Start() - AggregateException - Unhandled Exception"
+ exc.Message, ae);
return false;
}
});
}
catch (Exception ex)
{
_log.Error(Name + " - Start() - unhandled exception.", ex);
}
}
async private Task ReadLinesOnContinuationAction(Task<String> text)
{
try
{
DataHasBeenReceived = true;
IsConnected = true;
_readLines.Append(text.Result);
if (OnLineRead != null) OnLineRead(Name, _readLines.ToString());
_readLines.Clear();
string text = await _client.ReadLinesAsync(_cts.Token);
await ReadLinesOnContinuationAction(text);
}
catch (Exception)
{
_log.Error(Name + " - ReadLinesOnContinuationAction()");
}
}
Or something like that. The above is obviously not compiled and might require a little bit of massaging to get right. But hopefully the general idea is clear enough.

Async operation completes, but result is not send to browser

I want to implement a webchat.
The backend is a dual WCF channel. Dual channel works in the console or winforms,
and it actually works on the web. I can at least send and receive messages.
As a base I used this blog post
so, the async operation completes.
When i debug the result, I see that the messages are all ready to send to the browser.
[AsyncTimeout(ChatServer.MaxWaitSeconds * 1020)] // timeout is a bit longer than the internal wait
public void IndexAsync()
{
ChatSession chatSession = this.GetChatSession();
if (chatSession != null)
{
this.AsyncManager.OutstandingOperations.Increment();
try
{
chatSession.CheckForMessagesAsync(msgs =>
{
this.AsyncManager.Parameters["response"] = new ChatResponse { Messages = msgs };
this.AsyncManager.OutstandingOperations.Decrement();
});
}
catch (Exception ex)
{
Logger.ErrorException("Failed to check for messages.", ex);
}
}
}
public ActionResult IndexCompleted(ChatResponse response)
{
try
{
if (response != null)
{
Logger.Debug("Async request completed. Number of messages: {0}", response.Messages.Count);
}
JsonResult retval = this.Json(response);
Logger.Debug("Rendered response: {0}", retval.);
return retval;
}
catch (Exception ex)
{
Logger.ErrorException("Failed rendering the response.", ex);
return this.Json(null);
}
}
But nothing is actually sent.
Checking Fiddler, I see the request but I never get a response.
[SessionState(SessionStateBehavior.ReadOnly)]
public class ChatController : AsyncController
I also had to set the SessionStateBehaviour to Readonly, otherwise the async operation would block the whole page.
EDIT:
Here is the CheckForMessagesAsync:
public void CheckForMessagesAsync(Action<List<ChatMessage>> onMessages)
{
if (onMessages == null)
throw new ArgumentNullException("onMessages");
Task task = Task.Factory.StartNew(state =>
{
List<ChatMessage> msgs = new List<ChatMessage>();
ManualResetEventSlim wait = new ManualResetEventSlim(false);
Action<List<ChatMessage>> callback = state as Action<List<ChatMessage>>;
if (callback != null)
{
IDisposable subscriber = m_messages.Subscribe(chatMessage =>
{
msgs.Add(chatMessage);
wait.Set();
});
bool success;
using (subscriber)
{
// Wait for the max seconds for a new msg
success = wait.Wait(TimeSpan.FromSeconds(ChatServer.MaxWaitSeconds));
}
if (success) this.SafeCallOnMessages(callback, msgs);
else this.SafeCallOnMessages(callback, null);
}
}, onMessages);
}
private void SafeCallOnMessages(Action<List<ChatMessage>> onMessages, List<ChatMessage> messages)
{
if (onMessages != null)
{
if (messages == null)
messages = new List<ChatMessage>();
try
{
onMessages(messages);
}
catch (Exception ex)
{
this.Logger.ErrorException("Failed to call OnMessages callback.", ex);
}
}
}
it`s the same idea as the in the refered blog post
EDIT2:
Btw, when nothing is received, so the wait timeout comes into play, the reponse returns. so it seems to crash somewhere. any idea how to log this?
I changed the jQUERY request (see original blog post) from POST to GET. That fixes it.

Categories

Resources