.NET异步中的可扩展性与响应性



我正在阅读参考考试70-483:C#书中的编程,给出了以下代码示例:

清单1-19

public Task SleepAsyncA(int millisecondsTimeout)
{
return Task.Run(() => thread.Sleep(millisecondsTimeout);
}
public Task SleepAsyncB(int millisecondsTimeout)
{
TaskCompletionSource<bool> tcs = null;
var t = new Timer(delegate { tcs.TrySetResult(true); }, -1, -1);
tcs = new TaskCompletionSource<bool>(t);
t.Change(millisecondsTimeout, -1);
return tcs.Task;
}

下面的段落指出:

SleepAsyncA方法使用线程池中的线程,而睡觉。然而,第二种方法不同的实现,在等待时不占用线程要运行的计时器。第二种方法提供了可伸缩性。

为什么A具有响应能力,但B具有可扩展性?

假设起点没有进程/线程/任务控制,并且是一个无响应的旋转循环,检查时间是否已经过去,例如:

public void SleepBadly(int millisecondsTimeout)
{
var stopTime = DateTime.UtcNow.AddMilliseconds(millisecondsTimeout);
while (DateTime.UtcNow < stopTime) {}
return;
}

SleepAsyncA睡眠线程而不是旋转,因此它不使用任何CPU,因此会有响应,因为CPU可用,但它在睡眠时仍在使用线程。

SleepAsyncB在等待时放弃了线程,因此它不使用CPU,该线程可以用于其他用途;因此它具有响应性可扩展性

例如,在规模上,如果您在SleepAsyncA中有100000个未完成的调用;要么你已经耗尽了线程池,它们就会开始排队,要么你会有100000个活动线程,这两个线程都不利于扩展。

另一方面,当100000个调用在等待时,SleepAsyncB将使用0个线程,并且什么也不做比什么都不做更具可扩展性。

然而,尽管SleepAsyncB是如何使用TaskCompletionSource等任务构造的一个很好的例子,但您可能想在这个例子中实际做的是:

public Task SleepAsyncC(int millisecondsTimeout)
{
return Task.Delay(millisecondsTimeout);
}

A具有响应性,因为它不阻塞对用户很重要的线程,因此具有异步性。UI仍然是流动的,但它是不可扩展的,因为它在后台(通过阻塞线程)占用了有限的资源。

B仍然响应,但也可伸缩。因为它是真正异步的,而不仅仅是看起来是异步的。它不会占用任何有限的资源,即使UI仍然是流动的。

最新更新