С# ожидает ввода из другого потока с помощью AutoResetEvent

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

Я разработал аппаратное обеспечение, с которым общаюсь через С#. Аппаратное обеспечение подключается через USB и запускает процедуры инициализации после перечисления с ОС. В этот момент он просто ждет, пока программа C# начнет отправлять команды. В моем коде C# пользователь должен нажать кнопку «Подключить», которая отправляет команду и требуемую полезную нагрузку, чтобы оборудование знало, что оно должно продолжать работать. Затем аппаратное обеспечение отправляет команду обратно в виде ACK. Проблема в том, что моя программа на С# должна ждать получения 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);

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


person Anthony    schedule 08.01.2012    source источник


Ответы (4)


Я рекомендую вам использовать класс 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); 

Если вы не хотите использовать асинхронную 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);
person Stephen Cleary    schedule 08.01.2012
comment
Это довольно крутые вещи. Единственная проблема с кодом, который вы представили, заключается в том, что Send_Connection_Packet не имеет дело с командой возврата от оборудования, но я могу использовать ваш пример для реализации концепции. Спасибо! - person Anthony; 08.01.2012

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

person diggingforfire    schedule 08.01.2012
comment
Я следил за учебником, и это, кажется, имеет смысл. Спасибо! - person Anthony; 08.01.2012

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

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

person Robert Allan Hennigan Leahy    schedule 08.01.2012
comment
Спасибо! Какое ключевое слово я ищу с точки зрения изучения того, как создать новую тему? Я уверен, что здесь уже много раз отвечали, но я не могу найти то, что ищу, предположительно, потому что я использую неправильные модные словечки. - person Anthony; 08.01.2012
comment
BackgroundWorker прост в использовании - person diggingforfire; 08.01.2012
comment
@Энтони, ты ищешь [Thread.Start](http://msdn.microsoft.com/en-us/library/system.threading.thread.start.aspx) (см. ссылку). - person Robert Allan Hennigan Leahy; 08.01.2012

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

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

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

person alexm    schedule 08.01.2012
comment
Спасибо! Это решило мою проблему с тайм-аутом, что поможет мне добиться прогресса, пока я не смогу реализовать одно из вышеперечисленных решений, чтобы предотвратить зависание графического интерфейса. - person Anthony; 08.01.2012