C # ожидает ввода из другого потока, используя AutoResetEvent - PullRequest
1 голос
/ 08 января 2012

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

Я разработал аппаратную часть, с которой я общаюсь через C #.Аппаратное обеспечение подключается через USB и запускает процедуры инициализации после перечисления с ОС.В этот момент он просто ждет, пока программа C # начнет отправлять команды.В моем коде C # пользователь должен нажать кнопку «Соединить», которая отправит команду и требуемую полезную нагрузку, чтобы дать знать оборудованию, что оно должно продолжать работать.Аппаратное обеспечение затем отправляет команду обратно как ACK.Проблема в том, что моя C # -программа должна ждать получения ACK, но графический интерфейс пользователя полностью заморожен, пока аппаратное обеспечение не ответит, так как я не знаю, как разделить его на другой поток, который может свободно блокироваться.Если аппаратное обеспечение отвечает немедленно, то оно работает нормально, но если оно не может подключиться, то программа остается замороженной на неопределенное время.

С учетом сказанного я знаю, что должно произойти несколько вещей, но я неуверен, как их реализовать.Прежде всего, я не думаю, что сидеть в цикле в ожидании логического значения - это правильный путь, но использование AutoResetEvent на самом деле не кажется намного лучше.Должен быть лучший способ с использованием таймеров, большего количества потоков или чего-то подобного.

Я использую событие DataReceived с объектом serialPort следующим образом:

private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
    byte cmd = (byte)serialPort1.ReadByte();

    if (cmd == (byte)Commands.USB_UART_CMD_MCU_CONNECT)
        MCU_Connect_Received.Set();

}

В функции buttonClick («основной» поток), программа останавливается, пока ожидает ACK:

//Send the command to signal a connection
Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);

textBox1.AppendText("-I- Attempting to contact hardware...");

MCU_Connect_Received.WaitOne();

textBox1.AppendText("Success!" + Environment.NewLine);

В идеале я хотел бы знать, истек ли тайм-аут, чтобы я мог напечатать «Failed!»вместо «Удачи!».Отсутствие тайм-аута также означает, что он останется там навсегда, как я упоминал выше, пока я не убью процесс.Возможно, что он не найдет какое-либо оборудование, но если это произойдет, он должен ответить менее чем за 1 секунду, поэтому таймаута в 2 секунды будет более чем достаточно.Я пытался использовать Thread.Sleep, но это также заморозило графический интерфейс.

Ответы [ 4 ]

3 голосов
/ 08 января 2012

Я рекомендую использовать класс Task.Вы можете использовать TaskCompletionSource для выполнения задачи после ее завершения.

Используя новую асинхронную поддержку , ваш код станет:

textBox1.AppendText("-I- Attempting to contact hardware...");
await Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);
textBox1.AppendText("Success!" + Environment.NewLine); 

Если выЕсли вы не хотите использовать Async CTP, вы можете вызвать Task.ContinueWith и передать TaskScheduler.FromCurrentSynchronizationContext, чтобы запланировать запуск строки textBox1.AppendText("Success!") в потоке пользовательского интерфейса.

Поддержка асинхронности также включает таймеры (TaskEx.Delay) и комбинаторы (TaskEx.WhenAny), так что вы можете легко проверить время ожидания:

textBox1.AppendText("-I- Attempting to contact hardware...");
var commTask = Send_Connection_Packet((byte)Commands.USB_UART_CMD_PC_CONNECT);
var timeoutTask = TaskEx.Delay(1000);
var completedTask = TaskEx.WhenAny(commTask, timeoutTask);
if (completedTask == commTask)
  textBox1.AppendText("Success!" + Environment.NewLine); 
else
  textBox1.AppendText("Timeout :(" + Environment.NewLine);
2 голосов
/ 08 января 2012

Если вы хотите, чтобы графический интерфейс оставался отзывчивым, вы должны запускать вещи в фоновом потоке. A BackgroundWorker делает это красиво. Я придерживаюсь повторного события над занятой конструкцией ожидания. Вы можете использовать таймер для запуска повторного события после истечения времени ожидания

1 голос
/ 08 января 2012

Чтобы включить тайм-ауты, используйте другую перегрузку WaitOne ():

 bool succeeded = MCU_Connect_Received.WaitOne(timeOutInMilliseconds, false);
 if (succeeded)  
 {
       textBox1.AppendText("Success!" + Environment.NewLine);         
 }
 else
 {
       textBox1.AppendText("Failed!" + Environment.NewLine);         
 }

Рассмотрите возможность перемещения кода, связанного со связью, в отдельный класс для инкапсуляции протокола связи. Таким образом, код будет легче поддерживать, и вы сможете воплотить в жизнь все идеи Задачи / фонового рабочего, предложенные другими людьми.

1 голос
/ 08 января 2012

Проблема с зависанием GUI заключается в том, что все обратные вызовы для событий GUI происходят в потоке, в котором выполняется GUI. Если вы не хотите, чтобы графический интерфейс зависал, вам нужно создать новый поток .

Для реализации тайм-аута вы можете рассчитать время ожидания для дескриптора события , а затем проверить возвращаемое значение для true или false, чтобы определить, был ли вызов успешным или истек ли он .

...