C# Асинхронное подключение к нескольким SerialPort

106
18 января 2020, 01:20

Проект делаю на Unity 2018.3.10. Скрипты на С#.

Имеется самодельное устройство подключенное к USB и определяемое ОС как подключенное в COM-порт. Устройство шлёт ключевую последовательность из 3 байт, а затем набор данных. Поскольку в компиляторе Unity у класса SerialPort не реализован event на принятие данных, пришлось в ручную создавать отдельный поток и читать данные приходящие из буфера. Порт для подключения брал из диспетчера устройств и задавал вручную.

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

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

Недавно познакомился с Task, async и await, но не разобрался ещё до конца. Думается что Task-и мне подойдут больше, чем отдельный несколько потоков, да ещё и запускающихся последовательно.

Прошу помочь, т.е. подсказать как реализовать асинхронное подключение сразу к нескольким портам, определить есть на этом порту моё устройство или нету и в конце вернуть список портов с устройствами.

По сути вопрос: Как одновременно запустить несколько задач внутри которых будет выполняться мой метод подключения и проверки, а затем вернуть результат?

Сейчас выглядит это примерно так:

SerialThread - класс который содержит в себе метод с циклом while(true){} в котором происходит чтение байт из буфера порта.

Connect(out sT, out T, name) - метод для инициализации и запуска потока.

private List<string> GetPortListWithDevices() {                
    var spNames = new List<string>(SerialPort.GetPortNames());  
    if (spNames.Count == 0) return spNames;
    var timer = new System.Timers.Timer(1000);
    try {
        timer.Elapsed += delegate(object sender, ElapsedEventArgs args) {
                ((System.Timers.Timer) sender).Enabled = false;
            };
        var result = new List<string>();
        foreach (var name in spNames) {
        SerialThread sT = null;
        Thread T = null;
            try {
                Connect(out sT, out T, name);
                timer.Enabled = true;
                timer.Start();
                while (timer.Enabled) {
                    // Ждём.....
                }
                if (sT.isDevice)
                    result.Add(name);
                else Debug.LogWarning($"SP ({name}) is not Digitizer");
            }
            catch (Exception e) {
                Debug.LogWarning($"SP ({name}) not added to list");
            }
            finally {
                Disconnect(ref sT, ref T);
            }
        }
        return result;
    }
    catch (Exception e) {
        Debug.LogException(e);
        return null;
    }
}

UPD: метод Connect()

private static bool Connect(out SerialThread sT, out Thread T, string portName, int baudRate = 38400) {
    try {
        sT = new SerialThread(portName, baudRate, _reconnectionDelay, maxUnreadMessages);
        T = new Thread(sT.RunForever) {Name = portName};
        T.Start();
        return true;
    }
    catch (Exception e) {
        Debug.LogException(e);
        sT = null;
        T = null;
        return false;
    }
}
Answer 1

UPD: Не совсем понятно, чего мы ожидаем от метода RunForever класса SerialThread, но я предположил что он выглядит так:

    public bool IsDevice { get; set; }
    public string PortName { get; set; }
    public void RunForever(string portName)
    {
        while(/*true*/)
        {
            IsDevice = true;
        }
    }

Тогда код будет примерно таким:

    private async Task<SerialThread> ConnectAsync(string portName, int baudRate = 38400)
    {
        var serialThread = new SerialThread(portName, baudRate, _reconnectionDelay, maxUnreadMessages);
        await Task.Run(() => serialThread.RunForever(portName));
        return serialThread;
    }

Обратится можно так

    private async Task<IEnumerable<string>> GetPortListWithDevicesAsync()
    {
        var spNames = new List<string>(SerialPort.GetPortNames());
        return spNames.AsParallel()
            .Select(async x => await ConnectAsync(x, baudRate: 123))
            .Select(x => x.Result)
            .Where(x => x.IsDevice)
            .Select(x=>x.PortName)
            .ToArray();
    }

Или так:

    private IEnumerable<string> GetPortListWithDevices()
    {
        var spNames = new List<string>(SerialPort.GetPortNames());
        var tasks = spNames.Select(x => ConnectAsync(x, baudRate: 123)).ToArray();
        Task.WaitAll(tasks);
        return tasks.Select(x => x.Result)
            .Where(x => x.IsDevice)
            .Select(x => x.PortName);
    }
READ ALSO
Как сделать так, чтобы при движении одного объекта на другой он не пересек его?

Как сделать так, чтобы при движении одного объекта на другой он не пересек его?

Есть два объекта, рисуемые на форме (на пример, круг и треугольник) и необходимо при движение (движение происходит при нажатии стрелок на клавиатуре)...

118
Очень странное поведение OleDbDataReader

Очень странное поведение OleDbDataReader

Всем доброго дня! Сразу суть вопроса - запрос в коде не отрабатывается верно - не приходят данные, проблема только с полем WWG_NAME, остальные...

127
Открыть новое окно/вкладку с новым пользователем

Открыть новое окно/вкладку с новым пользователем

В автотесте Есть адрес: https://flowtimeqapnmsoftlabs

110
Заполнение матрицы с помощью функции

Заполнение матрицы с помощью функции

Хотелось бы узнать, как можно заполнить элементы матрицы, используя некоторую функцию, которая генерирует числа в заданном диапазонеВ C++ данный...

91