Search code examples
c#exceptionasync-awaittcpclientnetworkstream

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.


Solution

  • 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.