繁体   English   中英

执行SerialPort.Close()时线程被卡住

[英]Thread is stuck when executing SerialPort.Close()

我要关闭gsm终端的串行端口时,应用程序中的线程会陷入死锁的问题。 这个问题在这里这里都是众所周知的,但是这些线程中的所有建议都没有帮助我。

/// <summary>
///     Closes the COM port, prevents reading and writing
/// </summary>
public void Stop()
{
    Debug.WriteLine("stop called");
    var block = true;
    var bgw = new BackgroundWorker
    {
        WorkerReportsProgress = false,
        WorkerSupportsCancellation = false,
    };

    bgw.DoWork += (s, e) =>
    {
        if (!CanAccessPort())
            return;

        try
        {
            _serialPort.DataReceived -= Read;
            GC.ReRegisterForFinalize(_serialPort.BaseStream);
            _serialPort.Close();
            _isOpen = false;

        }
        catch (Exception ex)
        {
            throw new Exception(PORTERROR, ex);
        }
    };

    bgw.RunWorkerCompleted += (s, e) =>
    {
        Debug.WriteLine("block is set to false =)");
        block = false;
    };

    bgw.RunWorkerAsync();
    while (block)
        Thread.Sleep(250);
}

当执行_serialPort.Close()时,以上代码将永远运行。 作为推荐的建议,我阅读了有关在单独的线程中运行关闭操作的信息。 我尝试了BackgroundWorkerThread类,但没有任何效果。 按照另一个线程中的建议使用AutoResetEvent也不起作用。 在关闭端口之前,我向它发送了一些命令并收到了一些结果,但是它不会关闭。 当我运行一个简单的命令行程序来启动端口,读取数据并尝试关闭它时,一切正常,甚至没有线程。

是什么原因导致僵局? 我没有做几乎所有其他答案中都提到的与GUI相关的工作。

DataReceived事件处理程序代码如下:

/// <summary>
///     Reads input from the COM interface and invokes the corresponding event depending on the input
/// </summary>
private void Read(object sender, SerialDataReceivedEventArgs e)
{
    var buffer = new char[1024];
    var counter = 0;
    _keepRunning = true;
     if (_timeout == null)
     {
        // timeout must be at least 3 seconds because when sending a sms to the terminal the receive notification (+CSDI) can be lost with less timeout 
        _timeout = new Timer(3000);
        _timeout.Elapsed += (s, ev) =>
        {
            _keepRunning = false;
            _timeout.Stop();
        };
    }
    _timeout.Start();

    // cancel condition: no more new data for 3 seconds or "OK"/"ERROR" found within the result
    while (_keepRunning)
    {
        var toRead = _serialPort.BytesToRead;
        if (toRead == 0)
        {
            Thread.Sleep(100);
            continue;
        }
        _timeout.Stop();
        _timeout.Start();

        counter += _serialPort.Read(buffer, counter, toRead);

        // ok or error found in result string
        var tmp = new string(buffer).Replace("\0", "").Trim();
        if (tmp.EndsWith("OK") || tmp.EndsWith("ERROR"))
        {
            _timeout.Stop();
            _keepRunning = false;
        }
    }

    // remove empty array slots from the back
    var nullTerminalCounter = 0;
    for (var i = buffer.Length - 1; i != 0; i--)
    {
        if (buffer[i] == '\0')
        {
            nullTerminalCounter++;
            continue;
        }
        break;
    }
    Array.Resize(ref buffer, buffer.Length - nullTerminalCounter);
    var str = new String(buffer).Trim();

    // result must be something different than incoming messages (+CMTI: \"MT\", 25)
    if (!((str.StartsWith("+CMTI") || str.StartsWith("+CSDI")) && str.Length < 20))
    {
        // when an incoming message is received, it does not belong to the command issued, so result has not yet arrived, hence port is still blocked!
        _isBlocked = false;
        Debug.WriteLine("port is unblocked");
    }


    var args = new CommandReturnValueReceivedEventArgs
    {
        ResultString = str
    };
    OnCommandReturnValueReceived(this, args);
}
    if (toRead == 0)
    {
        Thread.Sleep(100);
        continue;
    }

此代码是死锁的基本来源。 SerialPort.Close()的规则是,仅当SerialPort的事件处理程序均未激活时,它才能关闭串行端口。 问题是,您的DataReceived事件处理程序几乎总是处于活动状态,正在等待数据。 这不是该事件的预期用途。 您应该从串行端口读取任何可用内容,通常是将字节追加到缓冲区并退出 有更多字节可用时,事件再次触发。

   while (_keepRunning)

您似乎发现了此问题,并尝试使用Timer修复它。 那也不行,因为是很难调试非常破烂的方式。 布尔变量不是适当的同步原语,例如ManualResetEvent。 当您以x86为目标并运行程序的Release版本时,while()循环不会看到_keepRunning变量变为false 它启用了抖动优化器,易于将变量存储在cpu寄存器中。 需要声明变量volatile以抑制该优化。

怀疑 ,但不能保证,使用volatile可以解决您的问题。 而且您可能希望在调用Close()之前将_keepRunning设置为false ,这样就不会出现超时延迟。

但是,指出了一种更具结构性的修复方法。 重写DataReceived事件处理程序,使其永远不会循环等待数据。 鉴于这似乎是与调制解调器对话的代码,因此只需一个简单的_serialPort.ReadLine()调用即可。

如果我确保任何线程都不会同时调用OpenClose ,那么这些问题就消失了。 我通过使用锁来做到这一点。

实质如下。

lock(_port)
    _port.Open(.....);

...

lock(_port)
    _port.Close(.....);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM