等待某事发生 - 异步模型还是同步模型?

3
我有一个名为WaitForReaderArrival的方法,如下所示:(一直运行,等待读卡器到来)
        public void WaitForReaderArrival()
        {
            do
            {
                if (ReaderArrived())
                {
                    break;
                }

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

我在等待读者使用以下方式到达:


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

我的一个同事要求我将上面的那一行更改为

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

问题是:
  1. 我采用的异步模型真的没有用吗?为什么她让我改成正常的同步方法,这对我来说仍然是一个问题。

  2. 在上面的内容中,等待某件事情发生然后继续进行的正确方式是什么?


很可能会有某种外部事件发生,比如用户输入、套接字消息、串口数据等,当“读取器到达”时。有类似的东西吗? - noseratio - open to work
不,我正在编写的库公开了这个事件,并在上述代码被触发时引发它。 - now he who must not be named.
2
你正在使用的代码是稍微改进过的忙等待循环,它通过节流来轮询某个东西。如果你没有其他获取变化通知的方式,那么将这样的循环转移到一个线程池线程中,使用await Task.Run可能是你能做的最好的事情。 - noseratio - open to work
实际上,您可以使用 Task.Delay 进行一些改进,例如此处 - noseratio - open to work
1
@Noseratio:我刚刚询问了一下,答案是肯定的。似乎有一个UI存在,并且正在等待“智能卡到达”的通知。正如你所指出的那样,如果我没有使用await,我的UI将被冻结。 - now he who must not be named.
显示剩余2条评论
4个回答

5

我采用的异步模型真的没有用吗?为什么她要求我将那行代码改成普通同步方法,这对我来说仍然是一个问题。

你正在使用的代码是繁忙等待循环的稍微改进版本,它正在进行轮询以进行节流。 如果您没有其他获取更改通知的方式,则可以像您已经使用await Task.Run一样将此循环卸载到池线程中,或者更好地使用Task.Delay

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

我的同事让我更改上面的代码... 上述内容中正确的等待事件发生后再继续执行的方式是什么?

你的同事是错的。如果你不用 await Task.Run 包装调用原始的 WaitForReaderArrival,或者调用上面提出的版本作为 WaitForReaderArrivalAsync().Wait(),你将会阻塞UI线程。为了保持UI线程消息循环的功能性,你应该使你的代码 "完全异步"

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

这是正确的称呼方式。从概念上讲,它与在计时器事件上检查ReaderArrived非常相似,但async/await为您提供了方便的线性伪同步代码流。
请注意,有一种流行的反模式,使用DoEvents进行繁忙等待,以保持UI响应性,有效地在UI线程上创建嵌套的消息循环:
public void WaitForReaderArrival()
{
    while (!ReaderArrived())
    {
        Application.DoEvents();
        System.Threading.Thread.Sleep(100);
    }
}

这样做是错误的:保持UI响应和Application.DoEvents的危险性

可能问题中的代码没有在 UI 线程上运行。 - svick
@svick,原帖已经澄清了这是一个UI线程,但如果不是UI线程,我仍然会坚持使用await Task.Delay(1000),除非轮询间隔必须非常小。 - noseratio - open to work

2

从代码的角度来看,它们没有区别。执行将在此处停止,并且直到相关事件发生后才会恢复。

从其他并发活动的角度来看,它们可能大不相同。在“await”情况下,线程不会阻塞,在第二个同步情况下,线程会阻塞。使用哪种方式取决于您在此处未公开的其他因素。

如果这是UI线程,则最好不要阻塞它。请使用“await”。

如果这是专用工作线程,则最好阻塞它。请使用同步形式。

我必须指出,严格分析时,第二个if(ReaderArrived())是错误的。它应该是一个断言,因为否则没有任何有用的事情可做。

另外,请考虑避免“忙睡眠”等待。通常这种做法都不好。

最后,您真的必须习惯先与您的同事交流再来这里。 :)


0

回答你的第二点
如果你的意图是在发生另一个事件时执行某些操作,我基本建议不要使用上述代码,而是使用事件。


0
以上哪种方式是等待某些事情发生然后继续的正确方法?
您可以将一个 continuation(又称回调函数)传递给您的过程:
public void WaitForReaderArrival(Action callback)
{
    do
    {
        if ( ReaderArrived() )
        {
            break;
        }

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

    callback();
}

使用示例:

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

上面我采用的异步模型不是很有用吗?为什么她要求我将那一行改成普通的同步方法,这对我来说仍然是个问题。
重点是,在应用程序的某个地方,你必须等待“Reader”到达。即使你在另一个后台线程中进行等待,你仍然必须等待该线程完成。
我唯一的担心是,在UI线程上使用“Thread.Sleep()”会冻结你的应用程序。考虑采用基于事件的方法。

在可以使用await的情况下使用continuations通常是一个不好的主意。await使代码更加清晰易懂。 - svick
真的,但是你仍然在使用“Task”。他的任务(双关语)是将代码转换为普通同步代码。我提出的解决方案基本上是一种事件模式,但不使用C#事件。 - Steven Liekens

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接