我正在阅读Exam Ref 70-483: Programming in C#一书,并提供了以下代码示例:
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可扩展?
答案 0 :(得分:9)
假设起始点没有进程/线程/任务控制并且是无响应的旋转循环,检查时间是否已经过去,例如:
public void SleepBadly(int millisecondsTimeout)
{
var stopTime = DateTime.UtcNow.AddMilliseconds(millisecondsTimeout);
while (DateTime.UtcNow < stopTime) {}
return;
}
SleepAsyncA睡眠线程而不是旋转所以它不使用任何CPU,所以响应因为CPU 可用,但它仍在使用线程在它睡觉的时候。
SleepAsyncB在等待时放弃线程,因此它不使用CPU,并且该线程可用于其他内容;所以它响应 和 可扩展。
例如,在规模上,如果您在SleepAsyncA中有100,000个未完成的呼叫;要么你已经耗尽了线程池,他们会开始排队,或者你会有100,000个活动线程,这两个都不是很好的可伸缩性。
另一方面,SleepAsyncB将使用0个线程,而100,000个调用正在等待,什么都不做,比做某事更具可扩展性。然而,虽然SleepAsyncB是如何使用TaskCompletionSource之类的Task结构的一个很好的例子,但你在这个例子中可能想要实际做的是:
public Task SleepAsyncC(int millisecondsTimeout)
{
return Task.Delay(millisecondsTimeout);
}
答案 1 :(得分:6)
A 响应因为它没有阻止对用户重要的线程而具有异步性的外观。用户界面仍然流畅,但它不具备可扩展性,因为它会占用有限的资源(通过阻止线程)。
B仍然响应,但可扩展,因为它是真正的异步而不仅仅是外观如此。它不会占用任何有限的资源,即使UI仍然流畅。