problem with two .NET threads and hardware access

2020-06-16 09:33发布

I'm creating an application which communicates with the device via FT2232H USB/RS232 converter. For communication I'm using FTD2XX_NET.dll library from FTDI website.
I'm using two threads:

  • first thread continuously reads data from the device
  • the second thread is the main thread of the Windows Form Application

    I've got a problem when I'm trying to write any data to the device while the receiver's thread is running. The main thread simply hangs up on ftdiDevice.Write function.

    I tried to synchronize both threads so that only one thread can use Read/Write function at the same time, but it didn't help.

    Below code responsible for the communication. Note that following functions are methods of FtdiPort class.

    Receiver's thread

    
            private void receiverLoop()
            {
                if (this.DataReceivedHandler == null)
                {
                    throw new BackendException("dataReceived delegate is not set");
                }
    
                FTDI.FT_STATUS ftStatus = FTDI.FT_STATUS.FT_OK;
                byte[] readBytes = new byte[this.ReadBufferSize];
    
                while (true)
                {
                    lock (FtdiPort.threadLocker)
                    {
                        UInt32 numBytesRead = 0;
    
                        ftStatus = ftdiDevice.Read(readBytes, this.ReadBufferSize, ref numBytesRead);
    
                        if (ftStatus == FTDI.FT_STATUS.FT_OK)
                        {
                            this.DataReceivedHandler(readBytes, numBytesRead);
                        }
                        else
                        {
                            Trace.WriteLine(String.Format("Couldn't read data from ftdi: status {0}", ftStatus));
                            Thread.Sleep(10);
                        }                    
                    }
                    Thread.Sleep(this.RXThreadDelay);
    
                }
            }
    


    Write function called from main thread

        public void Write(byte[] data, int length)
        {
            if (this.IsOpened)
            {
                uint i = 0;
    
                lock (FtdiPort.threadLocker)
                {
                    this.ftdiDevice.Write(data, length, ref i);
                }
    
                Thread.Sleep(1);
                if (i != (int)length)
                {
                    throw new BackendException("Couldnt send all data");
                }
            }
            else
            {
                throw new BackendException("Port is closed");
            }
        }
    


    Object used to synchronize two threads

    
    static Object threadLocker = new Object();
    

    Method that starts the receiver's thread

    
            private void startReceiver()
            {
                if (this.DataReceivedHandler == null)
                {
                    return;
                }
                if (this.IsOpened == false)
                {
                    throw new BackendException("Trying to start listening for raw data while disconnected");
                }
                this.receiverThread = new Thread(this.receiverLoop);
                //this.receiverThread.Name = "protocolListener";
                this.receiverThread.IsBackground = true;
                this.receiverThread.Start();
            }
    
    The ftdiDevice.Write function doesn't hang up if I comment following line:

    ftStatus = ftdiDevice.Read(readBytes, this.ReadBufferSize, ref numBytesRead);
  • 4条回答
    叼着烟拽天下
    2楼-- · 2020-06-16 09:56

    A few things:

    1. Check to see if your Read call is blocking. If so, you might not be able to call Write while the Read is blocking waiting for a response. Your API documentation may have more details on this.

    2. Some APIs do not support multiple threads very well, even when synchronizing access. If that's the case here, you can use a design where you delegate your Write commands to your comm thread. When I've used this pattern in the past, I typically queue up some sort of Command class containing the information I wish to write, and either use a threading Signal class to allow my calling 'command' methods to block or provide some sort of asynchronous notification.

    查看更多
    Anthone
    3楼-- · 2020-06-16 10:02

    Checking out the API, it looks to me that the driver is capable of emulating a COM port. I see the GetComPort() method returning a "COMx" string. That makes it pretty likely that you can use the System.IO.Ports.SerialPort class. Which already does what your wrapper is trying to do, it supports a DataReceived event. Worth a shot.

    查看更多
    Root(大扎)
    4楼-- · 2020-06-16 10:09

    An alternative is to use the event notification mechanism from FTDI, this way you don't need a blocking thread to read out data:

    public FTDISample()
    {
        private AutoResetEvent receivedDataEvent;
        private BackgroundWorker dataReceivedHandler;
        private FTDI ftdi;
    
        public FTDISample(string serialNumber){
            ftdi = new FTDI();
            FTDI.FT_STATUS status = ftdi.OpenBySerialNumber(serialNumber);
            receivedDataEvent = new AutoResetEvent(false);
            status = mFTDI.SetEventNotification(FTDI.FT_EVENTS.FT_EVENT_RXCHAR, receivedDataEvent);
            dataReceivedHandler = new BackgroundWorker();
            dataReceivedHandler.DoWork += ReadData;
            if (!dataReceivedHandler.IsBusy)
            {
                dataReceivedHandler.RunWorkerAsync();
            }
        }
    
        private void ReadData(object pSender, DoWorkEventArgs pEventArgs)
        {
            UInt32 nrOfBytesAvailable = 0;
            while (true)
            {
                // wait until event is fired
                this.receivedDataEvent.WaitOne();
    
                // try to recieve data now
                FTDI.FT_STATUS status = ftdi.GetRxBytesAvailable(ref nrOfBytesAvailable);
                if (status != FTDI.FT_STATUS.FT_OK)
                {
                    break;
                }
                if (nrOfBytesAvailable > 0)
                {
                    byte[] readData = new byte[nrOfBytesAvailable];
                    UInt32 numBytesRead = 0;
                    status = mFTDI.Read(readData, nrOfBytesAvailable, ref numBytesRead);
    
                    // invoke your own event handler for data received...
                    //InvokeCharacterReceivedEvent(fParsedData);
                }
            }
        }
    
        public bool Write(string data)
        {
            UInt32 numBytesWritten = 0;
            ASCIIEncoding enconding = new ASCIIEncoding();
            byte[] bytes = enconding.GetBytes(data);
            FTDI.FT_STATUS status = ftdi.Write(bytes, bytes.Length, ref numBytesWritten);
            if (status != FTDI.FT_STATUS.FT_OK)
            {
                Debug.WriteLine("FTDI Write Status ERROR: " + status);
                return false;
            }
            if (numBytesWritten < data.Length)
            {
                Debug.WriteLine("FTDI Write Length ERROR: " + status + " length " + data.Length +
                                " written " + numBytesWritten);
                return false;
            }
            return true;
        }
    
    查看更多
    Summer. ? 凉城
    5楼-- · 2020-06-16 10:12

    I've found more detailed API documentation. Indeed the ftdiDevice.read function is blocking unless you set the readTimeout value other then 0. Setting this timeout value solved the problem.
    Thanks for your quick response.
    Regards

    查看更多
    登录 后发表回答