我将从一个简单的例子开始:一个计时器。
private void StartButton_OnClick( object sender, RoutedEventArgs e)
{
async Task Start()
{
int count = 1;
while ( true )
{
this .Title = count + " iterations" ;
count++;
await Task.Delay(1000);
}
}
Start();
}
|
此 StartButton_OnClick 方法是 WPF 应用程序中的事件处理程序。它在单击“开始”按钮时运行。
此方法定义了一个名为 Start 的本地函数,它以即发即弃的方式调用该函数。这个局部函数是异步的。运行此函数时,它基本上从 1 开始计数到无穷大。每一秒,它都会将计数器递增 1,并将结果显示在窗口的标题上。
几乎每一秒,窗口的标题都会改变。
使用 Task.Delay,我基本上能够创建一个每秒运行一次的计时器。
如果在每次迭代中我们进行了一些需要半秒的计算,那么计数器将每 1.5 秒更新一次。
private void StartButton_OnClick( object sender, RoutedEventArgs e)
{
async Task Start()
{
int count = 1;
while ( true )
{
var calculationResult = RunCalculationThatTakesHalfASecond();
this .Title = count + " iterations and result is :" + calculationResult;
count++;
await Task.Delay(1000);
}
}
Start();
}
|
我们可以执行以下操作来尝试每秒递增计数器:
private void StartButton_OnClick( object sender, RoutedEventArgs e)
{
async Task Start()
{
int count = 1;
while ( true )
{
var sw = Stopwatch.StartNew();
var calculationResult = RunCalculationThatTakesHalfASecond();
this .Title = count + " iterations and result is :" + calculationResult;
count++;
var timeToWait = TimeSpan.FromSeconds(1) - sw.Elapsed;
if (timeToWait > TimeSpan.Zero)
{
await Task.Delay(timeToWait);
}
}
}
Start();
}
|
在上面的代码中,我使用Stopwatch类测量处理每次迭代所花费的时间。处理后,我不会延迟下一次迭代整整一秒。相反,我将下一次迭代延迟一秒减去处理当前迭代所花费的时间。
除了 async/await,我还可以使用这样的计时器类:
private void StartButton_OnClick( object sender, RoutedEventArgs e)
{
int count = 1;
var timer = new System.Windows.Threading.DispatcherTimer();
timer.Tick += (_, __) =>
{
var sw = Stopwatch.StartNew();
var calculationResult = RunCalculationThatTakesHalfASecond();
this .Title = count + " iterations and result is :" + calculationResult;
count++;
var timeToWait = TimeSpan.FromSeconds(1) - sw.Elapsed;
timer.Interval = timeToWait > TimeSpan.Zero ? timeToWait : TimeSpan.Zero;
};
timer.Interval = TimeSpan.FromSeconds(1);
timer.Start();
}
|
DispatcherTimer 类配置为每秒引发一次 Tick 事件。但它会在 Tick 事件处理程序完成后开始测量时间。因此,和以前一样,我使用 Stopwatch 来计算每次迭代所花费的时间,并调整下一次迭代的计时器等待间隔。
这两个示例之间的区别在于,在 async/await 的情况下,代码被建模为单个过程,而在计时器类的情况下,我们基本上有两个过程:启动计时器的过程 (StartButton_OnClick) 和事件处理程序每次新迭代开始时运行的 lambda。
将逻辑建模为单个过程使我们能够更好地控制并且更清晰。
例如,在 async/await 版本中,我使用 while 循环来对迭代进行建模。async/await 版本中的逻辑显然是使用过程代码建模的。
例如,比较我们如何在两个示例中指定每次迭代的等待时间。在 async/await 版本中,我们“等待”剩余时间并继续循环,而在定时器类版本中,我们设置了 Interval 属性。
在这个后来的版本中,就好像我们在两个不同的事物之间交流状态。lambda 中的代码告诉计时器对象在执行下一次迭代之前要等待多长时间。在 async/await 版本中,没有这样的通信。有一个单一的程序。
我将使用计时器类的方法称为基于事件的方法,因为我们编写代码来对事件做出反应。
在 async/await 版本中,我可以使用简单的 break 语句退出循环。在基于事件的版本中,我需要调用 timer.Stop() 这是两种事物之间的一种通信形式。
总而言之,async/await 版本允许我们对计时器逻辑进行建模,而基于事件的版本则需要我们进行协调。
在下一个示例中,这种差异将变得更加明显。