Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy shared async resource — Clarification?

Tags:

c#

async-await

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

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

But then he says :

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

Question:

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).

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

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

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

like image 712
Royi Namir Avatar asked Sep 20 '15 12:09

Royi Namir


People also ask

What does async mean in C#?

An async method runs synchronously until it reaches its first await expression, at which point the method is suspended until the awaited task is complete. In the meantime, control returns to the caller of the method, as the example in the next section shows.

What is difference between async and await in C#?

An async keyword is a method that performs asynchronous tasks such as fetching data from a database, reading a file, etc, they can be marked as “async”. Whereas await keyword making “await” to a statement means suspending the execution of the async method it is residing in until the asynchronous task completes.

What is async and await in C# Interview Questions?

Async and await in C# are the code markers, which marks code positions from where the control should resume after a task completes.


1 Answers

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

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

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.

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 talks about this in AsyncLazy:

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

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>.

like image 50
Yuval Itzchakov Avatar answered Nov 05 '22 10:11

Yuval Itzchakov