我在斯蒂芬的书结尾处看到了这个例子。
此代码可以通过 more 访问,而不是一个线程。
static int _simpleValue;
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
return _simpleValue++;
});
async Task GetSharedIntegerAsync()
{
int sharedValue = await MySharedAsyncInteger.Value;
}
无论代码多少部分同时调用Value,
Task<int>
仅在创建一次并返回给所有来电者。
然后他说:
如果有不同的线程类型可以调用
Value
(例如,用户界面) 线程和线程池线程,或两个不同的ASP.NET请求 线程),那么总是执行异步可能会更好 在线程池线程上委托。
所以他建议使用以下代码使整个代码在线程池线程中运行:
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(() => Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return _simpleValue++;;
}));
问题:
我不明白第一个代码的问题是什么。延续将在线程池线程中执行(由于ConfigureAwait,我们不需要原始上下文)。
同样,任何线程的任何控制都会到达await
,控件将返回给调用者。
我没有看到第二个代码试图解决的额外风险。
我的意思是 - 第一个代码中可能调用Value
“的不同线程类型有什么问题?
答案 0 :(得分:6)
&#34;可能称为Value&#34;的不同线程类型有什么问题? 在第一个代码?
该代码没有错误。但是,假设您有一些CPU绑定工作以及async
初始化调用。想象一下这样的例子:
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(
async () =>
{
int i = 0;
while (i < 5)
{
Thread.Sleep(500);
i++;
}
await Task.Delay(TimeSpan.FromSeconds(2));
return 0;
});
现在,你没有守卫&#34;反对这种行动。我假设Stephan提到了UI线程,因为你不应该做任何超过50ms的操作。你永远不希望你的UI线程冻结。
当您使用Task.Run
来调用代理人时,您可以从可能将长期委托传递给Lazy<T>
的地方覆盖自己。
Stephan Toub在AsyncLazy中谈到这一点:
我们有一个新的
AsyncLazy<T>
来自Lazy<Task<T>>
和。{ 提供了两个构造函数。每个构造函数都有一个函数 来自调用者,就像Lazy<T>
一样。第一个构造函数,在 事实上,采用与Lazy<T>
相同的Func。而不是传递那个Func<T>
直接指向基础构造函数,但是,我们改为 传递一个新的Func<Task<T>>
,它只使用StartNew来运行 用户提供的Func<T>
。第二个构造函数更加花哨。 而不是Func<T>
,它需要Func<Task<T>>
。有了这个 功能,我们有两个很好的选择,如何处理它。首先 只是将函数直接传递给基础构造函数, e.g:
public AsyncLazy(Func<Task<T>> taskFactory) : base(taskFactory) { }
该选项有效,但这意味着当用户访问Value时 此实例的属性将调用taskFactory委托 同步。如果
taskFactory
那可能是完全合理的 委托在返回任务实例之前做的工作很少。 但是,如果taskFactory
代表做了任何不可忽略的工作,a 在对taskFactory
的调用完成之前,对值的调用将会阻塞。至 涵盖这种情况,第二种方法是运行taskFactory
使用Task.Factory.StartNew
,即以异步方式运行委托, 就像第一个构造函数一样,即使这个委托已经存在 返回Task<T>
。