Waiting for something to happen - asynchronous or

2019-07-02 02:39发布

问题:

I have this method WaitForReaderArrival which is like below: (Runs all the time waiting for a reader to arrive)

        public void WaitForReaderArrival()
        {
            do
            {
                if (ReaderArrived())
                {
                    break;
                }

                System.Threading.Thread.Sleep(1000);
            } while (ReaderArrived() == false);
        }

And I am awaiting for the reader to arrive using,

 await Task.Run(new Action(WaitForReaderArrival));
 if (ReaderArrived())
 {
      //Raise an ReaderArrived here!
    ..//blah blah
 }

One of my co-worker asked me to change the above line just to

 WaitForReaderArrival(); 
 if (ReaderArrived())
 {
    //Raise an ReaderArrived here!
    ..//blah blah
 }

The question is:

  1. Is the asynchronous model that I adopted above is not really useful? Why she asked me to change that line to normal synchronous methodology is still a question to me.

  2. What in the above is the right way to wait for something to happen and then proceed?

回答1:

Is the asynchronous model that I adopted above is not really useful? Why she asked me to change that line to normal synchronous methodology is still a question to me.

The code you're using is a slightly improved version of the busy waiting loop, which is polling for something with throttling. If you don't have any other ways of getting notified of the change, you can offload this loop to a pool thread as you're doing already with await Task.Run, or better yet, use Task.Delay:

public async Task WaitForReaderArrivalAsync()
{
    while (!ReaderArrived())
    {
        await Task.Delay(1000).ConfigureAwait(false);
    }
}

One of my co-worker asked me to change the above line... What in the above is the right way to wait for something to happen and then proceed?

Your coworker is wrong. If you call your original WaitForReaderArrival without wrapping it with await Task.Run, or call the version proposed above as WaitForReaderArrivalAsync().Wait(), you will block the UI thread. To keep the UI thread message loop functional, you should make your code "Async All the Way":

// top-level event handler
async void Button_Click(object sender, EventArgs e)
{
    await WaitForReaderArrivalAsync();
    MessageBox.Show("ReaderArrived!");
}

That's the right way of calling it. Conceptually, it is very similar to checking ReaderArrived upon a timer event, but async/await gives you convenient linear pseudo-synchronous code flow.

Note, there is a popular anti-pattern which does busy waiting with DoEvents to keep the UI responsive, effectively creating a nested message loop on the UI thread:

public void WaitForReaderArrival()
{
    while (!ReaderArrived())
    {
        Application.DoEvents();
        System.Threading.Thread.Sleep(100);
    }
}

Doing so is wrong: Keeping your UI Responsive and the Dangers of Application.DoEvents.



回答2:

From the viewpoint of your code, there is no difference. Execution will cease at this point and not resume until after the relevant event has occurred.

From the viewpoint of other concurrent activities they could not be more different. In the 'await' case the thread does not block, in the second synchronous case it does. The decision as to which to use depends on other factors which you have not divulged here.

If this is the UI thread, you most likely don't want it to block. Use 'await'.

If this is a dedicated worker thread, you most likely do want it to block. Use the synchronous form.

I must point out that on strict analysis, the second if (ReaderArrived()) is wrong. It should be an assertion, since there is nothing useful to be done otherwise.

Also, consider avoiding 'busy sleep' waiting. It's usually a bad way to do this kind of thing.

Finally, you really must get used to the idea of talking to your co-workers first before coming here. :)



回答3:

Answer to your second point Basically I would suggest dont go with above code use the events if your intension is do something on occurence of another event.



回答4:

What in the above is the right way to wait for something to happen and then proceed?

You could pass a continuation (a.k.a. callback) to your procedure:

public void WaitForReaderArrival(Action callback)
{
    do
    {
        if ( ReaderArrived() )
        {
            break;
        }

        System.Threading.Thread.Sleep(1000);
    } while ( ReaderArrived() == false );

    callback();
}

Usage example:

WaitForReaderArrival(() =>
    {
        // Raise an ReaderArrived here!
        // ...blah blah
    });

Is the asynchronous model that I adopted above is not really useful? Why she asked me to change that line to normal synchronous methodology is still a question to me.

The point is that somewhere in your application, you'll have to wait for Reader to arrive. Even when you do the waiting in another background thread, you'll still have to wait for that thread to finish.

My only concern is that using Thread.Sleep() on the UI thread will freeze your application. Consider an event-based approach.