Ошибка при получении данных управления потоком данных устройства - PullRequest
0 голосов
/ 11 февраля 2020

Мои коллеги и я потратили несколько дней на поиск решения, и в большинстве ответов говорится, чтобы избавиться от ненужных тем. Вот ситуация.

Я контролирую до 1-55 устройств (в зависимости от потребностей пользователя). Я должен инициализировать каждое устройство примерно в одно и то же время. Я делаю это, делая Task для каждого вызова InitializeDevice(deviceNumber), который запускает инициализацию каждого устройства и затем опрашивает устройство до завершения.

Функция переопределения SerialPort.Write() имеет значение lock ed что только одно устройство может писать одновременно, так что это не моя проблема (тщательно проверено). Когда я создаю задачи для 3 устройств, я в порядке. Когда я создаю задачи для 4 устройств, мой последовательный порт перестает работать правильно (хотя он работает на моих коллегах, у которых 6 ядер по сравнению с моими 4 ядрами). Событие DataReceived перестает вызываться на короткое время, как объяснено ниже.

Write(command1); // Attempt 1, task 1 fails to get a response when responseTimeout.WaitOne(50) times out
Write(command1); // Attempt 2, task 1 fails to get a response due to 50ms timeout
Write(command1); // Attempt 3, task 1 fails to get a response due to 50ms timeout
Write(command1); // Attempt 4, task 1 fails to get a response due to 50ms timeout
Write(command1); // Attempt 5, task 1 fails to get a response due to 50ms timeout
DataReceived event is triggered and at this point BytesToWrite contains all the data that has been accumulated.
After this initial DataReceived event delay, everything proceeds as expected. (this is one of the biggest reasons that I believe that it is either the creation or starting of the `Task`s that breaks the port)

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

Чтобы проверить, было ли создание Task s узким местом, мы попытались создать все Task s, а затем запустить их одновременно, но это тоже не сработало, что создает впечатление запуска нить это проблема. Чтобы проверить, была ли проблема с запуском Task s, мы установили точку останова в InitializeDevice(deviceNumber), и она достигла всех 4 Task s, которые мы сделали до , функция SerialPort.Write() когда-либо называется. Поскольку все 4 были созданы, запущены и работали до того, как были сделаны Write, вы могли бы подумать, что Task не может быть узким местом. Когда я увеличил responseTimeout.WaitOne(time) с 50 до 1000, все Write были успешными, хотя задержки между Write и включением DataReceived были следующими: 990 мс, 6 мс, 15 мс, 15 мс , 16 мс, et c. Среднее время для ответов было около 15 мс. Мы подключили анализатор logi c с использованием и без использования Task s, и время отклика неизменно составляло примерно 5 мс, поэтому мы не теряем ответы, просто событие не запускается в разумные сроки , Мы пытались установить DataReceived как наивысший приоритет и инициализацию Task s как наименьший, но безрезультатно.

Все указывает на то, что это проблема с Task s или созданием потока, принимающим скорость процессора и предотвращающим DataReceived от срабатывания в разумные сроки. Мы не можем просто ждать ответа бесконечно, и мы должны одновременно запускать все устройства. На данный момент у нас закончились решения.


Пример кода

public class DeviceControl
{
    private static void Initialize_AllDevices(CustomSerialPort port, List<Device> devices)
    {
        for (int i = 0; i < devices.Count; i++)
        {
            DeviceNumber deviceNumber = i;

            Task.Factory.StartNew(
                () => Initialize_SingleDevice(port, devices, deviceNumber));
        }
    }

    private static void Initialize_SingleDevice(CustomSerialPort port, List<Device> devices, int deviceNumber)
    {
        List<byte> command = devices[deviceNumber].CreateCommand();
        port.Write(command);
    }
}

public class CustomSerialPort : SerialPort
{
    private AutoResetEvent _responseTimeout;

    public CustomSerialPort()
    {
        DataReceived += Read;
        _responseTimeout = new AutoResetEvent(false);
    }

    private void Read(object sender, SerialDataReceivedEventArgs eventArgs)
    {
        // This is what is not reached until Task creation is complete.
        // Parse response
        if (ReadSuccessful())
        {
            _responseTimeout.Set();
        }
    }

    public void Write()
    {
        lock (new object())
        {
            Write(command.ToArray(), 0, command.Count);

            // Wait until response timeout is hit or response is received.
            _responseTimeout.Reset();
            if (!_responseTimeout.WaitOne(50))
            {
                // uh oh
            }
        }
    }
}

...