проблема с two.NET потоки и аппаратный доступ


Я создаю приложение, которое взаимодействует с устройством через конвертер FT2232H USB/RS232. Для связи я использую FTD2XX_NET.библиотека dll с сайта FTDI.
Я использую две нити:

  • первый поток непрерывно считывает данные с устройства
  • второй поток является основным потоком приложения Windows Form

    У меня есть проблема, когда я пытаюсь записать какие-либо данные на устройство, пока поток приемника работает. Основной поток просто зависает вверх по ftdiDevice.Функция записи.

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

    Ниже код, отвечающий за связь. Обратите внимание, что следующие функции являются методами класса FtdiPort.

    Нить приемника

    
            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);
    
                }
            }
    


    Функция записи, вызываемая из основного потока

    
    
    
        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");
            }
        }
    


    Объект, используемый для синхронизации двух нити

    
    static Object threadLocker = new Object();
    

    Метод, запускающий поток получателя

    
            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();
            }
    
    FtdiDevice.Функция Write не зависает, если я комментирую следующую строку:
    ftStatus = ftdiDevice.Read(readBytes, this.ReadBufferSize, ref numBytesRead);
  • 4 8

    4 ответа:

    Несколько вещей:

    1. Проверьте, блокируется ли ваш вызов чтения. Если это так, вы не сможете вызвать Write, пока чтение блокирует ожидание ответа. Ваша документация API может содержать более подробную информацию об этом.

    2. Некоторые API не очень хорошо поддерживают несколько потоков, даже при синхронизации доступа. Если это так, вы можете использовать конструкцию, в которой вы делегируете свои команды записи потоку связи. Когда я использовал этот шаблон в прошлом, я обычно я ставлю в очередь какой-то класс команд, содержащий информацию, которую я хочу записать, и либо использую потоковый класс сигналов, чтобы позволить моим вызывающим методам 'command' блокировать, либо предоставляю какое-то асинхронное уведомление.

    Альтернативой является использование механизма уведомления о событиях из FTDI, таким образом, вам не нужен блокирующий поток для считывания данных:

    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;
        }
    

    Я нашел более подробную документацию по API. Действительно, ftdiDevice.функция чтения блокируется, если вы не установите значение readTimeout, отличное от 0. Установка этого значения таймаута решила проблему.
    Спасибо за быстрый ответ.
    С уважением

    Проверяя API, мне кажется, что драйвер способен эмулировать COM-порт. Я вижу метод GetComPort (), возвращающий строку "COMx". Поэтому вполне вероятно, что вы можете использовать класс System.IO.Ports.SerialPort. Который уже делает то, что пытается сделать ваша оболочка, он поддерживает событие DataReceived. Стоит попробовать.