Avoid Application.DoEvents() in C#

2019-03-04 05:00发布

问题:

A lot of good programmers (including a lot of good members of Stackoverflow) are against using Application.DoEvents() whatever the circumstances are. Actually it is even supported with plenty of articles in the net, like this one, this famous debate on SO, ...

Though, I'm stuck in a case, where (I) think DoEvents() is the unique exit (lack of experience). That's enough as introduction, let's see some coding.

I have a 'serialPort' component to connect with a controller through serial communication, send command and wait for its response, that's all.

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;
}

in DataReceived method of my serialPort, I read the existing response and break the loop

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; 
}

It's not exactly like this, but this is a sample code that shows how I sned-receive via serial communication, would you kindly show me where am I forcing myself into this pitfall?

回答1:

If you are using .NET 4.5 it is really easy to do with async/await and a TaskCompletionSource and 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>();
}


回答2:

You should use the async I/O methods. Asynchronously wait for Task<T> to complete with timeout is a good example.



回答3:

I assume the answer is to run that loop in a different thread and send a message to the UI when the response is available. This assumes you cant do async IO for some reason



标签: c# doevents