Multithreaded socket connection hangs c#. Socket timeout/hangs

0

I'm attempting to send print instructions to two printers over a socket connection. The script works fine then hangs and reports back:

System.IO.IOException: Unable to write data to the transport connection: A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call

or

System.Net.Sockets.SocketException (0x80004005): A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond

The issue is intermittent and I haven't been able to reproduce with a debugger attached.

Can anyone spot what could be causing this behavior? I've had issue with thread safety which I believe could be to blame.

Apologies for the amount of code. Due to the possibility of this being down to threading I've included as much scope as I can.

// main entry point
class HomeController{

    List<string> lsLabelResults = new List<string>("label result");
    PrinterBench pbBench        = new PrinterBench("192.168.2.20","192.168.2.21");

    void Process(){

        oPrintController = new PrintController(this);

        if(GetLabel()){
            // should always come out of the big printer (runs in background)
            oPrintController.PrintBySocketThreaded(lsLabelResults, pbBench.PostageLabelIP);
            // should always come out of the small printer
            oPrintController.PrintWarningLabel();
        }
    }
}

class PrintController{

    HomeController oHC;

    private static Dictionary<string, Socket> lSocks = new Dictionary<string, Socket>();

    private BackgroundWorker _backgroundWorker;
    static readonly object locker = new object();
    double dProgress;
    bool bPrintSuccess = true;


    public PrintController(HomeController oArg_HC)
    {
        oHC = oArg_HC;
    }


    public bool InitSocks()
    {
        // Ensure the IP's / endpoints of users printers are assigned
        if (!lSocks.ContainsKey(oHC.pbBench.PostageLabelIP))
        {
            lSocks.Add(oHC.pbBench.PostageLabelIP, null);
        }
        if (!lSocks.ContainsKey(oHC.pbBench.SmallLabelIP))
        {
            lSocks.Add(oHC.pbBench.SmallLabelIP, null);
        }

        // attempt to create a connection to each socket
        try
        {
            foreach (string sKey in lSocks.Keys.ToList())
            {
                if (lSocks[sKey] == null || !lSocks[sKey].Connected)
                {
                    IPEndPoint ep = new IPEndPoint(IPAddress.Parse(sKey), 9100);
                    lSocks[sKey] = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                    try
                    {

                        //lSocks[sKey].Connect(ep);
                        var result = lSocks[sKey].BeginConnect(ep, null, null);

                        bool success = result.AsyncWaitHandle.WaitOne(2000, true);
                        // dont need success
                        if (lSocks[sKey].Connected)
                        {
                            lSocks[sKey].EndConnect(result);
                        }
                        else
                        {
                            lSocks[sKey].Close();
                            throw new SocketException(10060); // Connection timed out.
                        }

                    }
                    catch(SocketException se)
                    {
                        if(se.ErrorCode == 10060)
                        {
                            oHC.WriteLog("Unable to init connection to printer. Is it plugged in?", Color.Red);
                        }
                        else
                        {
                            oHC.WriteLog("Unable to init connection to printer. Error: " + se.ErrorCode.ToString(), Color.Red);
                        }
                    }
                    catch (Exception e)
                    {
                        oHC.WriteLog("Unable to init connection to printer. Error: " + e.ToString(), Color.Red);
                    }
                }
            }
        }catch (Exception e)
        {
            oHC.WriteLog(e.ToString(), true);
            return false;
        }

        return true;
    }

    public bool PrintBySocketThreaded(List<string> lsToPrint, string sIP)
    {
        // open both the sockets
        InitSocks();

        bBatchPrintSuccess = false;
        _backgroundWorker = new BackgroundWorker();

        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.RunWorkerCompleted += backgroundWorker_RunWorkerCompleted;
        _backgroundWorker.WorkerReportsProgress = true;
        _backgroundWorker.WorkerSupportsCancellation = true;

        object[] parameters = new object[] { lsToPrint, sIP, lSocks };

        _backgroundWorker.RunWorkerAsync(parameters);
        return true;
    }


    // On worker thread, send to print!
    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        object[] parameters = e.Argument as object[];

        double dProgressChunks = (100 / ((List<string>)parameters[0]).Count);
        int iPos = 1;

        Dictionary<string, Socket> dctSocks = (Dictionary<string, Socket>)parameters[2];

        foreach (string sLabel in (List<string>)parameters[0] )
        {
            bool bPrinted = false;

            // thread lock print by socket to ensure its not accessed twice
            lock (locker)
            {
                // get relevant socket from main thread
                bPrinted = PrintBySocket(sLabel, (string)parameters[1], dctSocks[(string)parameters[1]]);
            }

            iPos++;
        }

        while (!((BackgroundWorker)sender).CancellationPending)
        {
            ((BackgroundWorker)sender).CancelAsync();
            ((BackgroundWorker)sender).Dispose();
            //Thread.Sleep(500);
        }
        return;
    }


    // Back on the 'UI' thread so we can update the progress bar (have access to main thread data)!
    private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null) MessageBox.Show(e.Error.Message);
        if (bPrintSuccess) oHC.WriteLog("Printing Complete");

        bBatchPrintSuccess = true;

        ((BackgroundWorker)sender).CancelAsync();
        ((BackgroundWorker)sender).Dispose();
    }

    /// sends to printer via socket
    public bool PrintBySocket(string sArg_ToPrint, string sIP, Socket sock = null)
    {
        Socket sTmpSock = sock;

        if (sTmpSock == null)
        { 
            InitSocks();

            if (!lSocks.ContainsKey(sIP))
            {
                throw new Exception("Sock not init");
            }
            else
            {
                sTmpSock = lSocks[sIP];
            }
        }

        try
        {

            if(!sTmpSock.Connected || !sTmpSock.IsBound)
            {
                InitSocks();

                if (!sTmpSock.Connected)
                {
                    oHC.WriteLog("Unable to init connection to printer. Is it plugged in?", Color.Red);
                }
            }

            using (NetworkStream ns = new NetworkStream(sTmpSock))
            {
                byte[] toSend = null;

                // convert string to byte stream, or use byte stream
                if (byToPrint == null)
                {
                    toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
                }
                else
                {
                    toSend = byToPrint;
                }

                ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
                ns.Flush();
            }

            return true;

        }
        catch (Exception e)
        {
            oHC.WriteLog("Print by socket: " + e.ToString(), true);
            DisposeSocks();
        }
    }


    public bool PrintWarningLabel()
    {
        string sOut = sEOL + "N" + sEOL;
        sOut += "A0,150,0,4,3,3,N,\"WARNING MESSAGE TO PRINT\"" + sEOL;
        sOut += sEOL;

        if (PrintBySocket(sOut, oHC.pbBench.SmallLabelIP))
        {
            oHC.WriteLog("WARNING LABEL PRINTED");
            return true;
        }
        return false;
    }
}
c#
multithreading
sockets
asked on Stack Overflow May 28, 2019 by atoms • edited May 28, 2019 by atoms

1 Answer

1

It appears you’re connecting to TCP servers and sending some data. In modern .NET you don’t need explicit multithreading for that, async-await simplified things a lot. Here’s how I would probably do that.

static class NetworkPrinter
{
    const ushort tcpPort = 31337;
    const string sEOL = "\r\n";

    /// <summary>Send a single job to the printer.</summary>
    static async Task sendLabel( Stream stream, string what, CancellationToken ct )
    {
        byte[] toSend = Encoding.ASCII.GetBytes( sEOL + what );
        await stream.WriteAsync( toSend, 0, toSend.Length, ct );
        await stream.FlushAsync();
    }

    /// <summary>Connect to a network printer, send a batch of jobs reporting progress, disconnect.</summary>
    public static async Task printLabels( string ip, string[] labels, Action<double> progress, CancellationToken ct )
    {
        IPAddress address = IPAddress.Parse( ip );
        double progressMul = 1.0 / labels.Length;
        using( var tc = new TcpClient() )
        {
            await tc.ConnectAsync( address, tcpPort );
            Stream stream = tc.GetStream();
            for( int i = 0; i < labels.Length; )
            {
                ct.ThrowIfCancellationRequested();
                await sendLabel( stream, labels[ i ], ct );
                i++;
                progress( i * progressMul );
            }
        }
    }

    /// <summary>Send multiple batches to multiple printers, return true of all of them were good.</summary>
    public static async Task<bool> printBatches( LabelBatch[] batches )
    {
        await Task.WhenAll( batches.Select( a => a.print( CancellationToken.None ) ) );
        return batches.All( a => a.completed );
    }
}

/// <summary>A batch of labels to be printed by a single printer.</summary>
/// <remarks>Once printed, includes some status info.</remarks>
class LabelBatch
{
    readonly string ip;
    readonly string[] labels;
    public bool completed { get; private set; } = false;
    public Exception exception { get; private set; } = null;

    public LabelBatch( string ip, IEnumerable<string> labels )
    {
        this.ip = ip;
        this.labels = labels.ToArray();
    }

    /// <summary>Print all labels, ignoring the progress. This method doesn't throw, returns false if failed.</summary>
    public async Task<bool> print( CancellationToken ct )
    {
        completed = false;
        exception = null;
        try
        {
            await NetworkPrinter.printLabels( ip, labels, d => { }, ct );
            completed = true;
        }
        catch( Exception ex )
        {
            exception = ex;
        }
        return completed;
    }
}

You might still need manual thread synchronization if you don’t have a synchronization context (e.g. you’re writing a console app) to report progress. You can get progress callbacks called in parallel from multiple printers. Progress reporting should be fast, a single lock() on a static object will do. Not required if you’re building a GUI app and calling printBatches from the GUI thread, the sync.context will serialize everything to the GUI thread, despite network requests will still run in parallel.

answered on Stack Overflow May 30, 2019 by Soonts

User contributions licensed under CC BY-SA 3.0