0

If i have a service that stores a token as a lazy object, and the token is fetched inside a value factory, is that value persisted across multiple requests, or since the underlying service is scoped, lazy starts with a null value for every single request?

_token = new Lazy<Token>(() =>
{
    var token = new Token();   

    var task = Task.Run(async () =>
    {
        var response = await Login();

        token.AccessToken = response.AccessToken;
    });
    
    task.Wait();

    return token;
});

This is called in the constructor of the service:

public MyService() 
{
    private static Lazy<Token> _token

    if (_token is { IsValueCreated: false }) // execute logic above
}

However, MyService is registered as scoped in Startup.cs,

services.AddScoped<IMyService, MyService>();

Does this mean that the token is fetched every time service is requested (despite the fact that value factory is thread safe) or the value factory can be called only once (even if it is called from within multiple scopes), since it's declared as static?

4
  • What version of NET are you using? Or is this a framework version? Commented May 20, 2024 at 11:51
  • .NET 6, asp.net core Commented May 20, 2024 at 12:01
  • 1
    task.Wait(); - try avoiding this. Especially in a constructors. Commented May 20, 2024 at 12:05
  • I don't think that running a task on a thread pool thread and waiting for it synchronously inside of the factory method for lazy initialization is a good idea. Why are you doing this? Lazy initialization should still go fast, the purpose of it is to defer the creation of a heap object until it's needed for the first time. You may want to rethink your design here. Commented May 20, 2024 at 12:08

1 Answer 1

2

In general, any instance members of a service are bound to the lifetime of the service. So if the service itself is scoped, which in an ASP.NET Core context usually means that it only exists for the duration of a single request, then the members of that service will also only exist for that duration.

So your Lazy value is being constructed during instantiation of MyService and will be thrown away when the service goes out of scope, which happens at the end of the request.

If you want your member to persist for multiple requests, you would have to increase the lifetime of your service to singleton which means that you will only ever have one instance that is being reused. Note that this will also mean that everyone accessing your web application will end up being handled by the same service instance, so if your token is user-specific, this won’t work.

That being said, using a Lazy<T> for an asynchronous process is not a good idea to begin with. By calling .Wait() on the task, you are essentially throwing out every benefit of asynchronous execution while also opening yourself to potential deadlocks.

And also, just accessing IsValueCreated on the Lazy object will not actually run the initialization logic of the Lazy if that was your goal in the constructor.

To properly utilize the asynchronous loading process, I would suggest you to change your service into something like this:

public class MyService
{
    private Token _token;

    public async Task<Token> GetToken()
    {
        if (_token is null)
        {
            _token = await LoadToken();
        }
        return _token;
    }

    private async Task<Token> LoadToken()
    {
        var token = new Token();
        
        var response = await Login();
        token.AccessToken = response.AccessToken;
        token.RefreshToken = response.RefreshToken;
    
        return token;
    }
}

So instead of having a Lazy<Token> property, you have an asynchronous method GetToken which returns a Task<Token>. That way, every consumer can properly await the token asychronously until it is loaded, and by storing the token, you still have a mechanism to cache it.

(Note that this solution – just like Lazy<T> – is not thread-safe. If your service is a scoped service, it will likely not be consumed in parallel anyway though, so that should be fine.)

Sign up to request clarification or add additional context in comments.

1 Comment

Thanks. MyService needs to remain scoped. Then just introduce another singleton that only works with fetching tokens, that will be consumed by the MyService. Tokens are not user-specific.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.