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

Roy*_*mir 7 c# async-await

我在斯蒂芬的书结尾处看到了这个例子.

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

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 "的问题是什么?

Yuv*_*kov 6

第一个代码中的“可能调用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>。实际上,第一个构造函数采用与相同的Func Lazy<T>Func<T>但是,我们没有直接将其传递 给基本构造函数,而是传递了一个Func<Task<T>>仅使用StartNew来运行用户提供的的new Func<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完成为止。为了解决这种情况,第二种方法是运行taskFactoryusing Task.Factory.StartNew,即与第一个构造函数一样,异步运行委托本身,即使该委托已经返回了Task<T>