我在斯蒂芬的书结尾处看到了这个例子.
此代码可以由多个线程访问.
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;
}
Run Code Online (Sandbox Code Playgroud)
无论代码中有多少部分同时调用Value,
Task<int>它只创建一次并返回给所有调用者.
但后来他说:
如果可以调用不同的线程类型
Value(例如,UI线程和线程池线程,或两个不同的ASP.NET请求线程),那么总是在线程池线程上执行异步委托可能会更好.
所以他建议使用以下代码,使整个代码在线程池线程中运行:
static readonly Lazy<Task<int>> MySharedAsyncInteger = new Lazy<Task<int>>(() => Task.Run(
async () =>
{
await Task.Delay(TimeSpan.FromSeconds(2));
return _simpleValue++;;
}));
Run Code Online (Sandbox Code Playgroud)
题:
我不明白怎么使用的问题第一代码.延续将在线程池线程中执行(由于ConfigureAwait,我们不需要原始上下文).
此外,任何线程的任何控制都将很快到达 await,控件将返回给调用者.
我没有看到 第二个代码试图解决的额外风险.
我的意思是 - 第一个代码中" 可能调用的不同线程类型Value "的问题是什么?
第一个代码中的“可能调用Value的不同线程类型”是什么问题?
该代码没有错。但是,想象一下您在async初始化调用的过程中有一些CPU限制的工作。例如,将其图片如下:
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;
});
Run Code Online (Sandbox Code Playgroud)
现在,您无需“防御”此类操作。我假设Stephan提到了UI线程,因为您不应该执行任何超过50ms的操作。您永远都不希望UI线程冻结。
当您Task.Run用来调用委托时,您是在可能将长期运行的委托传递给您的地方掩盖自己Lazy<T>。
Stephan Toub在AsyncLazy中谈到了这一点:
在这里,我们有一个新的
AsyncLazy<T>派生自Lazy<Task<T>>并提供两个构造函数。每个构造函数都从调用者那里获取一个函数,就像一样Lazy<T>。实际上,第一个构造函数采用与相同的FuncLazy<T>。Func<T>但是,我们没有直接将其传递 给基本构造函数,而是传递了一个Func<Task<T>>仅使用StartNew来运行用户提供的的newFunc<T>。第二个构造函数有点花哨。而不是采取Func<T>,它需要一个Func<Task<T>>。有了这个功能,我们有两个很好的选择来处理它。首先是将函数直接向下传递给基本构造函数,例如:
public AsyncLazy(Func<Task<T>> taskFactory) : base(taskFactory) { }
Run Code Online (Sandbox Code Playgroud)
该选项有效,但是这意味着当用户访问此实例的Value属性时,taskFactory委托将被同步调用。如果
taskFactory委托人在返回任务实例之前只做很少的工作,那可能是完全合理的。 但是,如果taskFactory委托人做任何不可忽略的工作,则对Value的调用将被阻止,直到调用taskFactory完成为止。为了解决这种情况,第二种方法是运行taskFactoryusingTask.Factory.StartNew,即与第一个构造函数一样,异步运行委托本身,即使该委托已经返回了Task<T>。