С# Winform зависает на SerialPort.Close

У меня есть программа winform, которая выполняет асинхронный ввод-вывод на SerialPort. Тем не менее, я периодически сталкиваюсь с проблемой зависания программы при вызове SerialPort.Close(), по-видимому, случайным образом.

Я думаю, что это проблема безопасности потоков, но я не уверен, как это исправить, если это так. Я попытался добавить/удалить асинхронный обработчик DataReceived с функциями открытия/закрытия порта и отбросить входные и выходные буферы на порту, но, похоже, он ничего не делает. Я думаю, что важный код SerialPort ниже:

using System;
using System.Collections.Generic;
using System.IO.Ports;

public class SerialComm
{
  private object locker = new object();

  private SerialPort port;
  private List<byte> receivedBytes;

  public SerialComm(string portName)
  {
    port = new SerialPort(portName);
    port.BaudRate = 57600;
    port.Parity = Parity.None;
    port.DataBits = 8;
    port.StopBits = StopBits.One;

    receivedBytes = new List<byte>();
  }

  public void OpenPort()
  {
    if(port!=null && !port.IsOpen){
      lock(locker){
        receivedBytes.Clear();
      }

      port.DataReceived += port_DataReceived;
      port.Open();
    }
  }

  public void ClosePort()
  {
    if(port!=null && port.IsOpen){
      port.DataReceived -= port_DataReceived;
      while(!(port.BytesToRead==0 && port.BytesToWrite==0)){
        port.DiscardInBuffer();
        port.DiscardOutBuffer();
      }
      port.Close();
    }
  }

  private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  {
    try{
      byte[] buffer = new byte[port.BytesToRead];
      int rcvdBytes = port.Read(buffer, 0, buffer.Length);

      lock(locker){
        receivedBytes.AddRange(buffer);
      }

      //Do the more interesting handling of the receivedBytes list here.

    } catch (Exception ex) {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
      //put other, more interesting error handling here.
    }
  }
}

ОБНОВЛЕНИЕ

Благодаря ответу @Afrin, указывающему на состояние взаимоблокировки с потоком пользовательского интерфейса (Это сообщение в блоге хорошо описывает его и дает несколько других полезных советов), я внес простое изменение и не смог воспроизвести ошибку пока что!

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  try{
    byte[] buffer = new byte[port.BytesToRead];
    int rcvdBytes = port.Read(buffer, 0, buffer.Length);

    lock(locker){
      receivedBytes.AddRange(buffer);
    }

    ThreadPool.QueueUserWorkItem(handleReceivedBytes);

  } catch (Exception ex) {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
    //put other, more interesting error handling here.
  }
}

private void handleReceivedBytes(object state)
{
  //Do the more interesting handling of the receivedBytes list here.
}

person chezy525    schedule 12.01.2012    source источник


Ответы (4)


Причина, по которой он будет зависать при закрытии, заключается в том, что в обработчике событий вашего объекта SerialPort

Вы синхронизируете вызов с основным потоком (обычно путем вызова вызова). Метод закрытия SerialPort ожидает завершения своего потока EventLoopRunner, который запускает события DataReceived/Error/PinChanged. но поскольку ваш собственный код в событии также ожидает ответа основного потока, вы столкнетесь с ситуацией блокировки.

Решение: используйте begininvoke вместо вызова: https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-close-hangs-the-application

ссылка: http://stackoverflow.com/a/3176959/146622

РЕДАКТИРОВАТЬ: ссылка Microsoft не работает, поскольку они удалили Connect. попробуйте web.archive.org: https://web.archive.org/web/20111210024101/https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-close-hangs-the-application

person Afshin    schedule 12.01.2012
comment
Чтобы убедиться, что я вас правильно понял, вкратце, в SerialPort между вызовами Read и Close есть тупик? - person chezy525; 13.01.2012
comment
вам нужно изменить способ вызова обновлений элементов пользовательского интерфейса в обработчике событий port_DataReceived, использовать BeginInvoke для обновления вместо Invoke или, как описано в решении, использовать другой поток для обработки события. - person Afshin; 13.01.2012
comment
Обработка данных для пользовательского интерфейса в другом потоке, похоже, решила проблему. Спасибо! - person chezy525; 13.01.2012
comment
Большая часть вашего сообщения дословно скопирована из ответа Зака ​​Соу. - person Daniel A.A. Pelsmaeker; 14.12.2013
comment
Очень странно... Я уверен, что другие до вас смогли увидеть ссылку, и MS изменила местоположение. Кстати, решение состоит в том, чтобы использовать begininvoke вместо вызова, и ссылка была ответом от Microsoft Tech, дающим тот же совет. PS: как разработчик Windows, я предполагаю, что нам всем нужна учетная запись MS ... не так ли?! - person Afshin; 29.07.2015
comment
Кроме того, использование потока для закрытия дескриптора устранит любую проблему. Как опубликовано Здесь - person HOPE; 04.04.2020
comment
Ссылка гниет на ссылке Microsoft. - person Loren Pechtel; 11.07.2020

Обходной путь от пользователя nobugz с сайта здесь:

1) добавить поле System.Threading.Thread CloseDown; в форму с последовательным портом serialPort1;

2) реализовать метод CloseSerialOnExit() с serialPort1 близкими шагами:

private void CloseSerialOnExit()
{

    try
    {
        serialPort1.DtrEnable = false;
        serialPort1.RtsEnable = false;
        serialPort1.DiscardInBuffer();
        serialPort1.DiscardOutBuffer();
        serialPort1.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);

    }
}

3) когда вам нужно закрыть serialPort1 (например, при нажатии кнопки), вызовите CloseSerialOnExit() в новом потоке, чтобы избежать зависания:

...
CloseDown = new System.Threading.Thread(new System.Threading.ThreadStart(CloseSerialOnExit)); 
CloseDown.Start();
...

и это все!

person Andrii Omelchenko    schedule 06.08.2018
comment
Но это только перенаправит зависание в другой поток. Порт по-прежнему останется открытым. Что произойдет, если я (или какая-то другая программа) попытаюсь снова использовать порт? - person Emir; 17.02.2021

У меня была такая же проблема. Я решил эту проблему с помощью библиотеки SerialPortStrem. Вы можете установить с помощью установщика Nuget Pageckage.

Библиотека SerialportStream имеет следующие преимущества.

  • Независимая реализация System.IO.Ports.SerialPort и SerialStream для повышения надежности и удобства обслуживания.

После использования библиотеки SerialPortStream у меня не было проблем с зависанием пользовательского интерфейса, таких как взаимоблокировка в WPF. Я думаю, что такая же проблема в формах Windows. поэтому используйте библиотеку SerialPortStream.

Эта библиотека, очевидно, является решением проблемы замораживания пользовательского интерфейса.

person 김태형    schedule 12.09.2018
comment
Это исправило так много ошибок, с которыми мне пришлось бороться, помимо обеспечения буферизованного интерфейса чтения/записи, не беспокоясь о том, все ли байты были записаны или преобразование типов char испортило поток. - person Jay Tennant; 16.11.2019

Спасибо также за ответы. Я сталкивался с подобной проблемой до сегодняшнего дня. Я использовал решение Андрея Омельченко и помогло очень, но не на 100%. Я видел, что причиной зависания последовательного порта является обработчик события приема. Перед остановкой последовательного порта удалите обработчик события приема.

try
        {
            serialPort.DtrEnable = false;
            serialPort.RtsEnable = false;
            serialPort.DataReceived -= serialPort_DataReceived;
            Thread.Sleep(200);
            if (serialPort.IsOpen == true)
            {
                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();
                serialPort.Close();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
person Adrian Bogorin    schedule 21.02.2020