【问题标题】:c# thread safety socket confusionc#线程安全套接字混淆
【发布时间】:2019-05-16 15:01:29
【问题描述】:

我认为是线程安全问题。

我有两台打印机,一台装有小标签,另一台装有大标签。我想通过套接字向每个打印机发送 1 个打印作业。

我已尝试对第一个请求(大标签)进行线程化/后台处理,因为它可能需要很长时间才能完成。

99% 的时间脚本按预期工作。小标签和大标签来自各自的打印机。

但是,大小标签有时会发送到同一台打印机!无论是小还是大。

我认为这与线程安全有关,但我发现很难追踪和了解正在发生的事情。我尝试添加锁并在使用后关闭套接字,但无论我尝试什么,问题仍然存在。

我试图将我的代码减少到最低限度,但我知道这篇文章仍然非常繁重。任何建议将不胜感激。

// stores the printer info
class PrinterBench
{
    public PrinterBench(string sArg_PostageLabelIP, string sArg_SmallLabelIP)
    {
        PostageLabelIP = sArg_PostageLabelIP;
        SmallLabelIP = sArg_SmallLabelIP;
    }
    public string PostageLabelIP;
    public string SmallLabelIP;
}

// main entry point
class HomeController{

    PrintController oPrintController;
    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;
    public EndPoint ep { get; set; }
    public Socket sock { get; set; }
    public NetworkStream ns { get; set; }

    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
        foreach (string sKey in lSocks.Keys.ToList())
        {
            if (lSocks[sKey] == null || !lSocks[sKey].Connected )
            {
                ep = new IPEndPoint(IPAddress.Parse(sKey), 9100);
                lSocks[sKey] = new Socket(ep.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
                lSocks[sKey].Connect(ep);
            }
        }
        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];
            }
        }

        using (ns = new NetworkStream(sTmpSock))
        {
            byte[] toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
            ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
            ns.Flush();
        }
        return true;
    }


    public bool PrintWarningLabel()
    {
        string sOut = sEOL + "N" + sEOL;
        sOut += "LL411" + sEOL;
        sOut += "R40,0" + sEOL;
        sOut += "S5" + sEOL;
        sOut += "D15" + sEOL;
        sOut += "A0,0,0,4,4,3,N,\"!!!!!!!!!!!!!!!!!!!!!!!\"" + sEOL;
        sOut += "A0,150,0,4,3,3,N,\"WARNING MESSAGE TO PRINT\"" + sEOL;
        sOut += "A0,280,0,4,4,3,N,\"!!!!!!!!!!!!!!!!!!!!!!!\"" + sEOL;
        sOut += "P1";
        sOut += sEOL;

        if (PrintBySocket(sOut, oHC.pbBench.SmallLabelIP))
        {
            oHC.WriteLog("WARNING LABEL PRINTED");
            return true;
        }
        return false;
    }
}

【问题讨论】:

    标签: c# multithreading thread-safety


    【解决方案1】:

    PrintController 中有这个字段:

    public NetworkStream ns { get; set; }
    

    只用在这里:

        using (ns = new NetworkStream(sTmpSock))
        {
            byte[] toSend = Encoding.ASCII.GetBytes(sEOL + sArg_ToPrint);
            ns.BeginWrite(toSend, 0, toSend.Length, OnWriteComplete, null);
            ns.Flush();
        }
    

    如果两个线程同时执行此操作,一个可以在另一个要写入时将ns 更改为不同的NetworkStream

    由于ns 在这里被使用和处理,似乎没有任何理由将其声明为字段,这意味着多个线程可以覆盖它。相反,删除该字段并将您的代码更改为:

    using (var ns = new NetworkStream(sTmpSock))
    

    然后执行此操作的多个线程将创建自己的NetworkStream 作为局部变量,而不是争夺一个。

    我也会检查所有其他字段,看看它们是否需要是字段,或者是否可以将它们声明为局部变量。

    无意的共享状态对多线程代码不利。它的行为与您描述的完全一样。它工作,它工作,它工作,然后它不工作,并且当你想看到它时重现问题几乎是不可能的。

    【讨论】:

    • 多么出色的答案!正如你所说,诊断非常棘手。非常感谢您抽出宝贵时间。
    • 我遇到了一些关于套接字超时和挂起应用程序的问题。如果您能查看stackoverflow.com/questions/56345018/…,将不胜感激
    猜你喜欢
    • 1970-01-01
    • 2011-03-15
    • 2021-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-12
    • 2011-01-22
    • 2016-05-16
    相关资源
    最近更新 更多