The main interface card causes of death analysis C # port closed

Problem Description

A few days ago wrote a test program using the serial port SerialPort class, will close when the serial port interface card dead.
Reference blog reason windows programming interface card dead , cause of death come interface cards: the main thread and other threads or locks due to resource contention, there has been a deadlock.

Reference article know almost WinForm interface of suspended animation, how to determine which of its step card in your code? By clicking commissioning pause, view the ui thread function stack positioned directly blocking the number of lines of code , identify problems arise in Close SerialPort class () method.

Reference article C # serial operation Series (2) - Getting Started chapter, why my serial port when it will shut down the program in a deadlock? Solutions and most of the solutions online articles similar to: define two types of markers Listening bool and Closing, close the serial port and receive data to judge it before . I personally do not accept this approach, I feel there is a better way, and the article is about and it is not clear.

Find out why

Based on the principle of inquisitive, I continue to find the cause of the problem.
Take a look at the code interface card cause of death:

void comm_DataReceived(object sender, SerialDataReceivedEventArgs e)   
{   
    //获取串口读取的字节数
    int n = comm.BytesToRead;    
    //读取缓冲数据  
    comm.Read(buf, 0, n);       
    //因为要访问ui资源,所以需要使用invoke方式同步ui。   
    this.Invoke(new Action(() =>{...界面更新,略})); 
}   
  
private void buttonOpenClose_Click(object sender, EventArgs e)   
{   
    //根据当前串口对象,来判断操作   
    if (comm.IsOpen)   
    {   
        //打开时点击,则关闭串口   
        comm.Close();//界面卡死的原因
    }   
    else  
    {...}  
}

The problem arises in the above code, the principle is not yet clear, I can only refer to .NET source code to find the problem.

SerialPort class Open () method

SerialPort class source Close () method is as follows:

        public void Open()
        {
           //省略部分代码...
            internalSerialStream = new SerialStream(portName, baudRate, parity, dataBits, stopBits, readTimeout,
                writeTimeout, handshake, dtrEnable, rtsEnable, discardNull, parityReplace);
 
            internalSerialStream.SetBufferSizes(readBufferSize, writeBufferSize); 
            internalSerialStream.ErrorReceived += new SerialErrorReceivedEventHandler(CatchErrorEvents);
            internalSerialStream.PinChanged += new SerialPinChangedEventHandler(CatchPinChangedEvents);
            internalSerialStream.DataReceived += new SerialDataReceivedEventHandler(CatchReceivedEvents);
        } 

Each execution SerialPort class Open () object instance of a method of the type SerialStream will appear, and the event handler is bound to CatchReceivedEvents DataReceived event SerialStream instance.

SerialStream source category CatchReceivedEvents method is as follows:

        private void CatchReceivedEvents(object src, SerialDataReceivedEventArgs e)
        {
            SerialDataReceivedEventHandler eventHandler = DataReceived;
            SerialStream stream = internalSerialStream;
 
            if ((eventHandler != null) && (stream != null)){
                lock (stream) {
                    bool raiseEvent = false;
                    try {
                        raiseEvent = stream.IsOpen && (SerialData.Eof == e.EventType || BytesToRead >= receivedBytesThreshold);    
                    }
                    catch {
                        // Ignore and continue. SerialPort might have been closed already! 
                    }
                    finally {
                        if (raiseEvent)
                            eventHandler(this, e);  // here, do your reading, etc. 
                    }
                }
            }
        }
 

You can see SerialStream class CatchReceivedEvents method of triggering event itself DataReceived, DataReceived this event is our event handler used to receive serial data.

DataReceived event handlers is {...} blocks being performed, ErrorReceived, PinChanged also similar lock (stream).

SerialPort class Close () method

SerialPort class source Close () method is as follows:

        // Calls internal Serial Stream's Close() method on the internal Serial Stream.
        public void Close()
        {
            Dispose();
        }
        
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        protected override void Dispose( bool disposing )
        {
            if( disposing ) {
                if (IsOpen) {
                    internalSerialStream.Flush();
                    internalSerialStream.Close();
                    internalSerialStream = null;
                }
            }
            base.Dispose( disposing );
        }        

You can see, the implementation of Close () method eventually calls Dispose (bool disposing) method.
Microsoft SerialPort class to the parent class Dispose (bool disposing) method has been rewritten before performing base.Dispose (disposing) executes internalSerialStream.Close () method, that will turn off when SerialPort instance executes Close () method examples SerialStream inside SerialPort example, the parent class and then performing Close () operation .

base.Dispose (disposing) method does not focus, we look internalSerialStream.Close () method.

SerialStream source category is not found Close () method, described Close method does not override the parent class, looking directly at the parent class Close () method, the following source code:

        public virtual void Close()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }        

Close SerialStream parent class method calls Dispose (true), but SerialStream class overrides the parent class's Dispose (bool disposing) method, the source code is as follows:

        protected override void Dispose(bool disposing)
        {
            if (_handle != null && !_handle.IsInvalid) {
                try {
                //省略一部分代码
                }
                finally {
                    // If we are disposing synchronize closing with raising SerialPort events
                    if (disposing) {
                        lock (this) {
                            _handle.Close();
                            _handle = null;
                        }
                    }
                    else {
                        _handle.Close();
                        _handle = null;
                    }
                    base.Dispose(disposing);
                }
            }
        }

Close SerialStream parent class method calls Dispose (true), the above code will execute the lock (this) statement, that will lock itself when SerialStream instance executes Close () method .

Deadlock reason

The results of our previous analysis of the source code to sum up:

  • DataReceived event handler is executed in the lock (stream) {...} block
  • Examples of execution SerialPort Close () Close SerialStream Examples will first instance when the interior of the method SerialPort
  • Examples of execution SerialStream Close () will lock itself when the method of Example

When the auxiliary thread calls DataReceived event handler handles serial data but not updated interface, click interface "close" button calls SerialPort instance Close () method, UI thread will lock (stream) at a secondary thread has been waiting for the release stream thread lock.
When the worker thread finishes processing the data ready to update the interface to the problem, DataReceived event handler this.Invoke () waits for the UI thread has been commissioned to perform, but this time the UI thread also stopped at the instance of the SerialPort Close () method at wait DataReceived event handler execution is complete.
In this case, the thread deadlock, both sides do not go.

Resolve a deadlock

Most online methods are defined two types of markers Listening bool and Closing, close the serial port and receive data to judge it before.
My approach is DataReceived event handler with this.BeginInvoke () update interface, without waiting for the UI thread executing the delegate returns , stream thread lock will be released soon, SerialPort instance Close () method does not need to wait.

to sum up

The final answer to the question is very simple, but I find the source of the problem in the .NET source code review process, gain a lot. This is my first time to see such a depth of .NET source code, we found that the solution to the problem is still very useful. The result is not important, solution to the problem is the most important.

Guess you like

Origin www.cnblogs.com/timefiles/p/CsharpSerialPortDeadlockOnClose.html