懒惰共享异步资源 – 澄清?
我在斯蒂芬的书末尾看到了这个例子。
此代码可以由多个线程访问。
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
。