Handling Exceptions in Asynchronous Socket Callbacks

I was trying to get my head to wrap a socket communication and I put together a small Windows Forms application as a test. It is basically a client that will connect to the server, send some bytes and disconnect. Ultimately, I also get a response from the server, but I've basically lost it for now.

As far as I can tell, this code is working correctly, but since the submission of data occurs as a result of a button click in my UI, it should be asynchronous. I have a method called SendDataToServer(byte[] bytesToSend)

that connects to the server and transfers data. In my button click event handler, I have created a background executor that will call this method.

If my server is not up and running, I obviously get a socket exception, and of course there are other reasons why the exception could be thrown while trying to connect and transfer data. With backgroundworker and async ( ClientConnectCallback()

and ClientSendCallback()

) callbacks , what's the best way to ensure that everything is played out and displayed correctly in my UI?

Right now I have a MessageBox inside catch blocks inside the asynchronous callbacks themselves, but I'm wondering if this is really a place to display messages or if they should be passed back?

This is what my code looks like:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Windows.Forms;

namespace ClientTest
{
    public partial class MyForm : Form
    {
        private string ServerIpAddress = "x.x.x.x";
        private int ServerPort = 59176;

        public Socket ClientSocket;

        private static ManualResetEvent connectDone = new ManualResetEvent(false);
        private static ManualResetEvent sendDone = new ManualResetEvent(false);

        // state object for reading client data asynchronously
        public class StateObject
        {
            // client socket
            public Socket socket = null;
            // size of receive buffer
            public const int BufferSize = 1024;
            // receive buffer
            public byte[] buffer = new byte[BufferSize];
            // all bytes received get added to this
            public List<byte> bytes = new List<byte>();
        }

        public MyForm()
        {
            InitializeComponent();
        }

        private void ClientConnectCallback(IAsyncResult asyncResult)
        {
            try
            {
                // retrieve the socket from the state object
                Socket client = (Socket)asyncResult.AsyncState;

                // complete the connection
                client.EndConnect(asyncResult);

                // signal that the connection has been made
                connectDone.Set();
            }
            catch (SocketException sockEx)
            {
                // if the server isn't running, we'll get a socket exception here
                MessageBox.Show(sockEx.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            catch (ObjectDisposedException)
            {
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        private void ClientSendCallback(IAsyncResult asyncResult)
        {
            try
            {
                // retrieve the socket from the state object
                Socket client = (Socket)asyncResult.AsyncState;

                // complete sending the data to the server
                int bytesSent = client.EndSend(asyncResult);

                // signal that all bytes have been sent
                sendDone.Set();
            }
            catch (ObjectDisposedException objDispEx)
            {
                Debug.WriteLine(objDispEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        public void SendDataToServer(byte[] bytesToSend)
        {
            try
            {
                connectDone.Reset();
                sendDone.Reset();

                ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                IPAddress ipAddress = IPAddress.Parse(ServerIpAddress);
                IPEndPoint remoteEndPoint = new IPEndPoint(ipAddress, ServerPort);

                ClientSocket.BeginConnect(remoteEndPoint, new AsyncCallback(ClientConnectCallback), ClientSocket);
                connectDone.WaitOne();

                ClientSocket.BeginSend(bytesToSend, 0, bytesToSend.Length, 0, new AsyncCallback(ClientSendCallback), ClientSocket);
                sendDone.WaitOne();
            }
            catch (ObjectDisposedException objDispEx)
            {
                Debug.WriteLine(objDispEx.Message);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            finally
            {
                ClientSocket.Shutdown(SocketShutdown.Both);
                ClientSocket.Close();
            }
        }

        private void buttonSendDataToServer_Click(object sender, EventArgs e)
        {
            BackgroundWorker bwSendDataToServer = new BackgroundWorker();
            bwSendDataToServer.DoWork += bwSendDataToServer_DoWork;
            bwSendDataToServer.RunWorkerCompleted += bwSendDataToServer_RunWorkerCompleted;
            bwSendDataToServer.RunWorkerAsync();
        }

        private void bwSendDataToServer_DoWork(object sender, DoWorkEventArgs e)
        {
            byte[] bytesToSend = new byte[100];
            SendDataToServer(bytesToSend);
        }

        private void bwSendDataToServer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            Debug.WriteLine("BackgroundWorker has completed.");
        }
    }
}

      

+3


source to share


1 answer


"How do I report exceptions?" this is a rather broad question. It is difficult to know exactly which specific advice would be best in your case, as there are many options for solving the problem.

However, there are some general guidelines that IMHO you should always follow, and this can help you guide your design:

  • Don't block user interface I / O threads.

This means that, of course, your code example needs to be improved since you are delaying any I / O stream used to invoke your callbacks while waiting for the user to acknowledge the error message.

  1. Follow the general OOP principle of "separation of concerns".

In other words, even if the thread that represents the error message is not an I / O stream, it is still not recommended to have user interaction logic within a class that is designed to handle network I / O.

There are, of course, other useful rules for programming, but I think these two are the most relevant given your current code example. So how do you solve them?


At a minimum, I would change your current implementation so that none of the I / O code is in the class MyForm

. The class MyForm

is for the user interface; it should not be involved in network I / O mechanisms, but should delegate this work to another class.



Now this other class still needs to contact MyForm

. But he must do it in a way that does not depend on MyForm

, i.e. Which is not attached to (or "related") class. As far as reporting exceptions, one common way to accomplish this is to declare an event that fires every time an exception is thrown.

This goes for the second point, but the first question is still a problem. It can be solved by using a method Control.BeginInvoke()

to execute event-handling code on the form object so that the code runs on the UI thread and is asynchronous to the I / O thread (that is, the method BeginInvoke()

does not wait for the code that is called to complete before returning).

Putting it all together, you can end up with something like this, for example:

class ExceptionReportEventArgs : EventArgs
{
    public Exception Exception { get; private set; }

    public ExceptionEventArgs(Exception exception)
    {
        Exception = exception;
    }
}

class MyNetworkClient
{
    public event EventHandler<ExceptionReportEventArgs> ExceptionReport;

    private void OnExceptionReport(Exception exception)
    {
        EventHandler<ExceptionReportEventArgs> handler = ExceptionReport;

        if (handler != null)
        {
            handler(this, new ExceptionReportEventArgs(exception));
        }
    }

    // For example...
    private void ClientConnectCallback(IAsyncResult asyncResult)
    {
        try
        {
            /* code omitted */
        }
        catch (SocketException sockEx)
        {
            // if the server isn't running, we'll get a socket exception here
            OnExceptionReport(sockEx);
        }
        catch (ObjectDisposedException)
        {
        }
        catch (Exception ex)
        {
            OnExceptionReport(ex);
        }
    }
}

partial class MyForm : Form
{
    private MyNetworkClient _client;

    public MyForm()
    {
        InitializeComponent();

        _client = new MyNetworkClient();
        _client.ExceptionReport += (sender, e) =>
        {
            BeginInvoke((MethodInvoker)(() =>
                MessageBox.Show(e.Exception.Message, Application.ProductName,
                    MessageBoxButtons.OK, MessageBoxIcon.Error)));
        };
    }
}

      


Now all that said: as I mentioned, there are many ways to solve basic problems. An alternative to the above would be to use the async

/ pattern await

, which provides a mechanism for writing asynchronous logic cleanly in an easy-to-read, imperative style, while still allowing proper transitions between code that needs to run on the UI thread and code that doesn't.

I will not go into this issue in detail - there are many examples, and this will require much more refinement of the code that you posted - but the main idea would be to use, for example, NetworkStream

one that has built-in methods async

for handling I / O and allows exceptions to bubble up from I / O methods.

In this approach, you don't need an event ExceptionReport

. Instead, your class MyNetworkClient

above would suggest methods async

that the controller class (like your object MyForm

) can call to initiate I / O. If an exception is thrown, each method async

on the call stack can handle (if necessary) the exception, but then re-arrange it (i.e. with a simple expression throw;

), all the way down to UI code, where the exception will be raised on the UI thread and can be presented to the user there without blocking the I / O stream.


I think, in general, the key is to just keep the usual rules in mind and stick to them. You will eventually find that the code is easier to write and understand, and will behave better in a custom framework of your choice (like Winforms).

+2


source







All Articles