惰性共享异步资源-澄清吗? [英] Lazy shared async resource — Clarification?

查看:64
本文介绍了惰性共享异步资源-澄清吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I saw this example at the end of Stephen's book.

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

This code can be accessed by more than one thread.

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>仅创建一次 并返回给所有呼叫者.

No matter how many parts of the code call Value simultaneously, the Task<int> is only created once and returned to all callers.

但随后他说:

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

If there are different thread types that may call Value (e.g., a UI thread and a thread-pool thread, or two different ASP.NET request threads), then it may be better to always execute the asynchronous delegate on a thread-pool thread.

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

So he suggests the following code which makes the whole code run in a threadpool thread :

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

问题:

我不明白 first 代码有什么问题.继续将在线程池线程中执行(由于ConfigureAwait,我们不需要原始上下文).

I don't understand what's the problem with the first code. The continuation would be executed in a threadpool thread (due to ConfigureAwait , we don't need the original context).

只要来自任何线程的任何控件都将到达await,该控件就会返回给调用者.

Also as soon that any control from any thread will reach the await , the control will be back to the caller.

我看不出第二个代码试图解决的 extra 风险是什么.

I don't see what extra risk the second code is trying to resolve.

我的意思是- first 代码中的"可能调用Value 的不同线程类型"是什么问题?

I mean - what is the problem with "different thread types that may call Value" in the first code?

推荐答案

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

what is the problem with "different thread types that may call Value" in the first code?

该代码没有错误.但是,假设您在async初始化调用中进行了一些CPU绑定工作.举例来说,如下图所示:

There in nothing wrong with that code. But, imagine you had some CPU bound work along with the async initialization call. Picture it like this for example:

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;
});

现在,您无需防范"此类操作.我假设Stephan提到了UI线程,因为您不应该执行任何超过50ms的操作.您永远不想让UI线程冻结.

Now, you aren't "guarded" against these kind of operations. I'm assuming Stephan mentioned the UI thread because you shouldn't be doing any operation that's longer than 50ms on it. You don't want your UI thread to freeze, ever.

使用Task.Run调用委托时,您正在从可能将长期运行的委托传递给Lazy<T>的位置进行掩饰.

When you use Task.Run to invoke the delegate, you're covering yourself from places where one might pass a long running delegate to your Lazy<T>.

斯蒂芬·图布(Stephan Toub)在

Stephan Toub talks about this in AsyncLazy:

在这里,我们有一个新的AsyncLazy<T>,它是从Lazy<Task<T>>派生而来的 提供了两个构造函数.每个构造函数都有一个函数 和Lazy<T>一样从呼叫者处接收.第一个构造函数,在 实际上,其功能与Lazy<T>相同.而不是通过 Func<T>直接向下到基本构造函数,但是,我们改为 传递一个新的Func<Task<T>>,它仅使用StartNew来运行 用户提供的Func<T>.第二个构造函数有点花哨. 而不是使用Func<T>,而是使用Func<Task<T>>.有了这个 功能,我们有两个很好的选择来处理它.首先 只是将函数直接向下传递给基本构造函数, 例如:

Here we have a new AsyncLazy<T> that derives from Lazy<Task<T>> and provides two constructors. Each of the constructors takes a function from the caller, just as does Lazy<T>. The first constructor, in fact, takes the same Func that Lazy<T>. Instead of passing that Func<T> directly down to the base constructor, however, we instead pass down a new Func<Task<T>> which simply uses StartNew to run the user-provided Func<T>. The second constructor is a bit more fancy. Rather than taking a Func<T>, it takes a Func<Task<T>>. With this function, we have two good options for how to deal with it. The first is simply to pass the function straight down to the base constructor, e.g:

public AsyncLazy(Func<Task<T>> taskFactory) : base(taskFactory) { }

该选项有效,但这意味着用户访问值时 此实例的属性,将调用taskFactory委托 同步地.如果taskFactory 在返回任务实例之前,委托几乎不做任何工作. 但是,如果taskFactory代表进行了任何不可忽略的工作,则 对Value的调用将一直阻塞,直到对taskFactory的调用完成.到 涵盖这种情况,第二种方法是使用 Task.Factory.StartNew,即异步运行委托本身, 就像第一个构造函数一样,即使这个委托已经 返回Task<T>.

That option works, but it means that when a user accesses the Value property of this instance, the taskFactory delegate will be invoked synchronously. That could be perfectly reasonable if the taskFactory delegate does very little work before returning the task instance. If, however, the taskFactory delegate does any non-negligable work, a call to Value would block until the call to taskFactory completes. To cover that case, the second approach is to run the taskFactory using Task.Factory.StartNew, i.e. to run the delegate itself asynchronously, just as with the first constructor, even though this delegate already returns a Task<T>.

这篇关于惰性共享异步资源-澄清吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆