Избегайте Application.DoEvents() в C#

Многие хорошие программисты (включая многих хороших участников Stackoverflow) против использования Application.DoEvents() при любых обстоятельствах. На самом деле это даже поддерживается множеством статей в сети, например этот, эти знаменитые дебаты о SO,...

Хотя, я застрял в случае, где (я) думаю, что DoEvents() является единственным выходом (отсутствие опыта). Этого достаточно в качестве введения, давайте посмотрим код.

У меня есть компонент «serialPort» для подключения к контроллеру через последовательную связь, отправки команды и ожидания ее ответа, вот и все.

string response = "";
bool respFlag;

private string sendCommand(string command)
{
  respFlag = false;  //initialize respFlag

  serialPort1.Write(command);  // send the command

  Stopwatch timer = Stopwatch.StartNew(); // start a timer
  while(true)
  {
    // break from the loop if receive a response
    if(respFlag) break;  

    // timeOut error if no response for more than 500msec
    if(timer.ElapsedMilliseconds >= 500) break;

    // here comes the UGLY part
    Application.DoEvents(); 
  }

   return response;
}

в методе DataReceived моего serialPort я читаю существующий ответ и разрываю цикл

private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  response = serialPort1.ReadExisting();

  // set the flag to true to exit the infinite loop in sendCommand
  respFlag = true; 
}

Это не совсем так, но это пример кода, который показывает, как я sned-receive через последовательную связь, не могли бы вы показать мне, где я загоняю себя в эту ловушку?


person chouaib    schedule 06.02.2015    source источник
comment
Какую версию .NET вы используете?   -  person Scott Chamberlain    schedule 06.02.2015
comment
Вы обнаружите, что goto поддерживается. Так же и catch (Exception ex). Это не значит, что вы должны их использовать. Так что не используйте Application.DoEvents() - это зло.   -  person Enigmativity    schedule 06.02.2015
comment
@Enigmativity, именно чувак, я не обсуждаю использование / не использование, я ищу альтернативу, но не могу найти, поэтому я и спросил здесь :)   -  person chouaib    schedule 06.02.2015
comment
@ScottChamberlain Я на 4.5, VS2012   -  person chouaib    schedule 06.02.2015
comment
@chouaib — взгляните на async/await, TPL или Rx (Reactive Framework).   -  person Enigmativity    schedule 06.02.2015
comment
@Enigmativity: большое спасибо, я тоже рассмотрю их в качестве своих следующих (для изучения) тем, я ценю ваше время и усилия.   -  person chouaib    schedule 06.02.2015
comment
@Enigmativity: я только что заметил это, не могли бы вы подробнее рассказать о dont use catch(exception ex) ?   -  person chouaib    schedule 11.02.2015
comment
@chouaib — универсальная обработка исключений скрывает ошибки в коде — они могут заставить код работать, даже если он должен был дать сбой, а затем могут вызвать ошибки в другом месте вашей программы — или даже повредить время выполнения. Они в основном делают ваш код глючным. Вы всегда должны перехватывать только те исключения, которые являются исключительными и которые вы действительно можете правильно обработать.   -  person Enigmativity    schedule 11.02.2015
comment
@Enigmativity, я понимаю, что это не НИКОГДА не использовать catch exception, а использовать его скорее с умом, например: в принятом ответе здесь я ДОЛЖЕН поймать TimeOutException. мое понимание в порядке?   -  person chouaib    schedule 11.02.2015
comment
@chouaib - Абсолютно поймайте такие вещи, как TimeOutException - это действительно исключительное событие, с которым вы можете справиться. Я не могу придумать ни одного места, где можно было бы использовать простое catch (Exception ex), за исключением, может быть, класса самого верхнего уровня.   -  person Enigmativity    schedule 11.02.2015


Ответы (3)


Если вы используете .NET 4.5, это действительно легко сделать с помощью async/await, TaskCompletionSource и async/await.

TaskCompletionSource<string> resultTcs = new TaskCompletionSource<string>();
private async Task<string> SendCommandAsync(string command)
{
    serialPort1.Write(command);  // send the command

    var timeout = Task.Delay(500);

    //Wait for either the task to finish or the timeout to happen.
    var result = await Task.WhenAny(resultTcs.Task, timeout).ConfigureAwait(false);

    //Was the first task that finished the timeout task.
    if (result == timeout)
    {
        throw new TimeoutException(); //Or whatever you want done on timeout.
    }
    else
    {
        //This await is "free" because the task is already complete. 
        //We could have done ((Task<string>)result).Result but I 
        //don't like to use .Result in async code even if I know I won't block.
        return await (Task<string>)result;
    }
}
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    var response = serialPort1.ReadExisting();
    tcs.SetResult(response);

    //reset the task completion source for another call.
    resultTcs = new TaskCompletionSource<string>();
}
person Scott Chamberlain    schedule 06.02.2015
comment
и этот асинхронный метод можно назвать как string str = SendCommandAsync("*****");? правильно? - person chouaib; 06.02.2015
comment
Это будет называться как string str = await SendCommandAsync("*****"); - person Scott Chamberlain; 06.02.2015
comment
спасибо за руководство, я почитаю больше об async/await, а пока попробую ваше решение и доложу вам - person chouaib; 06.02.2015
comment
строка return await (Task<string>)result; выдает ошибку Cannot convert from Task<Task> to Task<string> !! любые намеки? - person chouaib; 06.02.2015
comment
@chouaib: ты уверен, что у тебя есть await с вызовом Task.WhenAny()? Если бы вы пропустили это, это объяснило бы ошибку, которую вы получаете. - person Peter Duniho; 07.02.2015

Вы должны использовать async методы ввода/вывода. Асинхронно ждать завершения Task‹T› с таймаутом хороший пример.

person Richard Schneider    schedule 06.02.2015

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

person pm100    schedule 06.02.2015