懒惰共享异步资源 – 澄清?

我在斯蒂芬的书末尾看到了这个例子。

此代码可以由多个线程访问。

static int _simpleValue; static readonly Lazy<Task> MySharedAsyncInteger = new Lazy<Task>( async () => { await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); return _simpleValue++; }); async Task GetSharedIntegerAsync() { int sharedValue = await MySharedAsyncInteger.Value; } 

无论代码中有多少部分同时调用Value, Task只创建一次并返回给所有调用者。

但后来他说:

如果有不同的线程类型可以调用Value (例如,UI线程和线程池线程,或两个不同的ASP.NET请求线程),那么总是在线程池线程上执行异步委托可能会更好。

所以他建议使用以下代码,使整个代码在线程池线程中运行:

 static readonly Lazy<Task> MySharedAsyncInteger = new Lazy<Task>(() => Task.Run( async () => { await Task.Delay(TimeSpan.FromSeconds(2)); return _simpleValue++;; })); 

题:

我不明白第一个代码的问题是什么。 延续将在线程池线程中执行(由于ConfigureAwait,我们不需要原始上下文)。

此外,一旦来自任何线程的任何控制都将到达await ,控件将返回给调用者。

我没有看到第二个代码试图解决的额外风险。

我的意思是 – 在第一个代码中“ 可能调用Value不同线程类型 ”有什么问题?

在第一个代码中“可能调用Value的不同线程类型”有什么问题?

这段代码没有任何问题 。 但是,假设您有一些CPU绑定工作以及async初始化调用。 想象一下这样的例子:

 static readonly Lazy> MySharedAsyncInteger = new Lazy>( async () => { int i = 0; while (i < 5) { Thread.Sleep(500); i++; } await Task.Delay(TimeSpan.FromSeconds(2)); return 0; }); 

现在,你没有“防范”这种行动。 我假设Stephan提到了UI线程,因为你不应该做任何超过50ms的操作。 您不希望自己的UI线程冻结。

当您使用Task.Run来调用委托时,您可以从可能将长期委托传递给Lazy地方覆盖自己。

Stephan Toub在AsyncLazy中讨论了这个问题

这里我们有一个新的AsyncLazy ,它源自Lazy>并提供了两个构造函数。 每个构造函数都从调用者那里获取函数,就像Lazy 。 事实上,第一个构造函数采用与Lazy相同的Func。 但是,我们不是将Func直接传递给基础构造函数,而是传递一个新的Func> ,它只使用StartNew来运行用户提供的Func 。 第二个构造函数更加花哨。 它不是采用Func ,而是采用Func> 。 有了这个function,我们有两个很好的选择来处理它。 第一个是简单地将函数直接传递给基础构造函数,例如:

 public AsyncLazy(Func> taskFactory) : base(taskFactory) { } 

该选项有效,但这意味着当用户访问此实例的Value属性时,将同步调用taskFactory委托。 如果taskFactory委托在返回任务实例之前做的工作很少,那么这可能是完全合理的。 但是,如果taskFactory委托执行任何不可忽略的工作,则对Value的调用将阻塞,直到对taskFactory的调用完成为止。 为了涵盖这种情况,第二种方法是使用Task.Factory.StartNew运行Task.Factory.StartNew ,即以异步方式运行委托本身,就像第一个构造函数一样,即使此委托已经返回Task