3

Let's say that I have a class with members which require asynchronous actions to initialize (such as file i/o or web requests). I only need to initialize once, and I don't want to reinitialize.

Are Tasks and Async-Await a good fit to accomplish this?

Here's an example of what I'm currently doing:

private Task _initializeTask;
public Task InitializeAsync()
{
    return _initializeTask ?? (_initializeTask = Task.Run(
            async () =>
            {
                // Do an action requiring await here
                await _storageField.LoadAsync();
            }));
}

Does this do what I think it does? Are there better ways to do it?

Is it thread safe? Not a requirement but should be considered.

Edits:

What I think it does? I believe that if _initializeTask hasn't been assigned then it will be assigned a new task that will kick off and then await the async lambda contained within. Any subsequent calls to the method will await the already running (or completed) task that was assigned to _initializedTask.

When do I want it to construct? Typically I'd use this sort of method on a service that I resolve with an IoC container. Multiple dependent classes can be constructed with a reference to the class. Then, before use, each of them awaits InitializeAsync(). If theres multiple dependent classes then I don't want to double up on initializing it.

Factory Method? There usually won't be multiple instances constructed that need to be initialized, so Factory method doesn't seem like a good solution. I've used something like a "static CreateAsync()" method for things like folder wrapper classes, but that didn't let me inject initialized folders into constructors. Async Factory methods don't gain anything when they can't be used with IoC constructor injection.

Trent Scheffert
  • 199
  • 1
  • 14

3 Answers3

5

Your code will work but it is not thread safe, _initializeTask can be changed after checking it for null and before initializing it. This will result in two initializations. I would consider using AsyncLazy<T>, which is inherits from Lazy<T> which is thread safe.

Then assuming LoadAsync returns Task rather than Task<T>, your code becomes (untested):

private AsyncLazy<object> initializeTask = new AsyncLazy<object>(async () =>
            {
                // Do an action requiring await here
                await _storageField.LoadAsync();
                return null;
            });

public Task InitializeAsync()
{
    return _initializeTask.Value;
}

You can also define a non-generic version of `AsyncLazy, so you don't have to return a value from the initialization routine.

public class AsyncLazy : Lazy<Task> 
{ 
    public AsyncLazy(Func<Task> taskFactory) : 
        base(() => Task.Run(taskFactory)) { } 
}

Then you can initialize it using an initialization method, however that method is required to be static by the compiler:

private AsyncLazy _initializeTask = new AsyncLazy(LoadStorageAsync);

private static async Task LoadStorageAsync()
{   
    // Do an action requiring await here
    await _storageField.LoadAsync();
}   

public Task InitializeAsync()
{
    return _initializeTask.Value;
}
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • The null-coalescing operator [link](http://msdn.microsoft.com/en-us/library/ms173224.aspx) isn't atomic between the null check and assignment? I guess that's too much to hope for. This creates a field with potential side effects doesn't it? While it's basically the same code being executed, it's now in a field rather than a method which reduces code clarity. AsyncLazy does seem like the exact thing I want though, is there a way to contain it's instantiation in a method so that it's more succinct? – Trent Scheffert Oct 09 '14 at 17:04
  • 1
    I updated the answer to use a method, but the compiler required that the method is static. – NeddySpaghetti Oct 09 '14 at 23:15
  • 1
    You can use `Task.Run(taskFactory)` instead of the approach using `StartNew()` and `Unwrap()`. – svick Oct 10 '14 at 13:23
  • 1
    Link to AsyncLazy is broken. – zellus May 11 '22 at 07:23
0

Run the asynchronous task from a regular initialization function. In the regular function, check if your app already has a loaded data set that matches your expectations. If the data is not present THEN call the async function.

...
If (BooleanFunctionToDetermineIfDataIsNotPresent){FunctionToLoadFreshData}
...


Private Async Void FunctionToLoadFreshData{...}

The function to load the data must not return a value lest it become a task itself.

svick
  • 236,525
  • 50
  • 385
  • 514
0

If you work in a WebHost (ASP.NETCore app) or generic Host environment you can use simply way to do that with nuget HostInitActions

    public void ConfigureServices(IServiceCollection services)
    {       
        services.AddSingleton<IService, MyService>();

        services.AddAsyncServiceInitialization()
            .AddInitAction<IService>(async (service) =>
            {
                await service.InitAsync();
            });
    }

This nuget ensures that your initialization action will be performed asynchronously before the application starts.

Another advantage of this approach is that this initialization action can be defined from any place where services are installed into the IServiceCollection (For example, in an extension method in another project that installs internal implementations of public interfaces). This means that the ASP.NET Core project does not need to know what service and how it should be initialized, and it will still be done.