The task is to write an application that works with a pool of COM ports to which modems are connected. First, choose a free modem:

for (int i = 0; i < lstPorts.Count; i++) { if (lstPorts[i].Status == "Ожидает") { PortNum = lstPorts[i].Title; lstPorts[i].Status = "В работе"; break; } } 

Then, in the same procedure, the modem polling procedure is called.

  await Task.Run(() => ModemPolling(PortNum)); 

This procedure first selects the modem number, then calls the procedure for working with the COM port and modem (located in another class)

  async void ModemPolling(string PortN) { int Jp = lstModem.Items.Count; phModel phMod; string telN=string.Empty; string SNum=string.Empty; clsProcess proc; proc = new clsProcess(); proc.onStr += onProcStr; proc.onEnd += onEndProc; for (int j = 0; j < Jp; j++) { phMod = (phModel)lstModem.Items[j]; if (phMod.Status == 0) { Dispatcher.Invoke(new Action(() => ((phModel)lstModem.Items[j]).Status = 1)); telN = ((phModel)lstModem.Items[j]).PnoneN; SNum = ((phModel)lstModem.Items[j]).SerialN; break; } } await Task.Run(() => proc.ModemProc(PortN, telN, SNum)); } 

Auxiliary modem number class

  public class phModel { string _serialN; string _pnoneN; int _hResult; int _status; int _pError; //серийный номер public string SerialN { get => _serialN; set=>_serialN = value; } //телефонный номер public string PnoneN { get => _pnoneN; set=>_pnoneN = value; } //результат выполнения операции public int HResult { get => _hResult; set=>_hResult = value; } //статус модема (свободен, занят, завершил работу, ошибка) public int Status { get => _status; set=>_status = value; } //код ошибки public int PError { get => _pError; set=>_pError = value; } public void SetStatus(int inSt) { _status = inSt; } } 

And actually, the very procedure of working with a modem

  public void ModemProc(string portNum, string telNum, string serialN) { portNumber = portNum; TelNum = telNum; SNumber = serialN; try { _port = new SerialPort(portNum) { BaudRate = _speed, Parity = _stParity, WriteTimeout = _tsT, ReadTimeout = _rsT, StopBits = _stBits, DataBits = _dBits, Handshake = Handshake.XOnXOff, DtrEnable = true, RtsEnable = true, NewLine = Environment.NewLine, DiscardNull = true }; if (!_port.IsOpen) _port.Open(); _port.DataReceived += _port_DataReceived; var na = new NotifyArgs(1, 1, "Порт " + portNum + " открыт.", 0, portNumber, TelNum); onStr?.Invoke(na); Thread.Sleep(500); int pRes = ModemConnect(telNum); } catch { } } public void PortClose(string pNum) { if (_port.IsOpen) _port.Close(); var na = new NotifyArgs(1, 2, "Порт " + pNum + " закрыт.", 0, portNumber, TelNum); onStr?.Invoke(na); Thread.Sleep(500); } int ModemConnect(string modemNum) { _port.Write("+++\r\n"); _port.Write("ATE1\r\n"); Thread.Sleep(500); _port.Write("ATH0\r\n"); // установка режима ожидания Thread.Sleep(500); _port.Write("AT+IFC=0,2\r\n"); // устанавливается режим контроля над передачей Thread.Sleep(500); _port.Write("at+cbst=7,0,1\r\n"); // устанавливается прозрачный режим Thread.Sleep(500); if (!Regex.IsMatch(modemNum, @"(8|\+)9[0-9]{9}")) return 11; _port.Write("ATDT " + modemNum + "\r\n" + " "); return 0; } void modemDisconnect() { try { Thread.Sleep(1000); _port.Write("+++\r\n"); Thread.Sleep(500); _port.WriteLine("ATH0\r\n"); // установка режима ожидания Thread.Sleep(500); } catch { } } 

NotifyArgs is a helper class that passes information to the main form of the application.

Calls to the modemDisconnect () and PortClose () methods are located at the end of the methods that process the data received from the modem.

In synchronous mode (for one COM port) everything works fine. But I need to use all the free modems of the pool (which have the status of "Pending"). Actually, the program is written for asynchronous mode.

However, in practice, very often (but not every time) an application crashes with an error

 System.ObjectDisposedException: 'Дескриптор SafeHandle был закрыт' 

Error log

  System.ObjectDisposedException HResult=0x80131622 Message=Дескриптор SafeHandle был закрыт Source=mscorlib StackTrace: at System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean& success) at System.StubHelpers.StubHelpers.SafeHandleAddRef(SafeHandle pHandle, Boolean& success) at Microsoft.Win32.UnsafeNativeMethods.GetOverlappedResult(SafeFileHandle hFile, NativeOverlapped* lpOverlapped, Int32& lpNumberOfBytesTransferred, Boolean bWait) at System.IO.Ports.SerialStream.EventLoopRunner.WaitForCommEvent() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() 

Error occurs after trying to execute a line of code

 _port.Write("ATDT " + modemNum + "\r\n" + " "); 

I have been struggling with the problem for the fourth day. On the Internet, adequate information about SafeHandle and how to fight it was never found .. Can someone tell me what to do ..

  • It seems that there is an attempt to write to a closed port. Is it the ATDT? Do previous write commands go through (if you dial the number)? - MBo
  • Yes it is. An error occurs on this line. I tried to remove it - the error disappeared immediately. And the error does not appear immediately, but after a certain (and rather long) time. Dialing and establishing a connection is a rather lengthy operation. I checked the dialing command on the modem goes away .. - VasyaM3
  • It looks as if you simultaneously called ModemPolling for the same port in different streams. - Pavel Mayorov

1 answer 1

The problem really turned out to be that dialing and establishing a connection with a modem is a very lengthy operation. The garbage collector does not wait for the next modem response (this is the _port_DataReceived procedure) and destroys the port.

to solve the problem it was enough to add two lines.

  _port.Write("ATDT " + modemNum + "\r\n" + " "); Thread.Sleep(10000); _port.Write("\r\n"); 

I, of course, understand that this solution is a “crutch”. I will continue to work by trying to override IDisposable.

  • Isn't it better to bring out the local variable of the port (or the list of active ports) a higher level so that the GC does not hurry? - MBo
  • Thread.Sleep(10000); looks sad of course. Especially if someone will use this application (10 seconds to wait for a response without notifying the user). - Andrei Khotko
  • It turns out no one is waiting for anyone. The program sends the ATDT command to the modem, and this is where its mission is completed. Further dialing and establishing a connection is made by the hardware itself modem. And at the moment of arrival of the answer СONNECT (And the answer comes in a separate stream) it turns out that the port has already been deleted. Sending a blank line to the modem, I “remind” the garbage collector that the port is still needed. The solution I see in redefining IDisposible. Maybe tell me how to do it. I still do not have enough mind .. - VasyaM3