How to initialize an object using async-await pattern
Asked Answered
H

5

24

I'm trying to follow RAII pattern in my service classes, meaning that when an object is constructed, it is fully initialized. However, I'm facing difficulties with asynchronous APIs. The structure of class in question looks like following

class ServiceProvider : IServiceProvider // Is only used through this interface
{
    public int ImportantValue { get; set; }
    public event EventHandler ImportantValueUpdated;

    public ServiceProvider(IDependency1 dep1, IDependency2 dep2)
    {
        // IDependency1 provide an input value to calculate ImportantValue
        // IDependency2 provide an async algorithm to calculate ImportantValue
    }
}

I'm also targeting to get rid of side-effects in ImportantValue getter, to make it thread-safe.

Now users of ServiceProvider will create an instance of it, subscribe to an event of ImportantValue change, and get the initial ImportantValue. And here comes the problem, with the initial value. Since the ImportantValue is calculated asynchronously, the class cannot be fully initialized in constructor. It may be okay to have this value as null initially, but then I need to have some place where it will be calculated first time. A natural place for that could be the ImportantValue's getter, but I'm targeting to make it thread-safe and with no side-effects.

So I'm basically stuck with these contradictions. Could you please help me and offer some alternative? Having value initialized in constructor while nice is not really necessary, but no side-effects and thread-safety of property is mandatory.

Thanks in advance.

EDIT: One more thing to add. I'm using Ninject for instantiation, and as far as I understand, it doesn't support async methods to create a binding. While approach with initiating some Task-based operation in constructor will work, I cannot await its result.

I.e. two next approaches (offered as answers so far) will not compile, since Task is returned, not my object:

Kernel.Bind<IServiceProvider>().ToMethod(async ctx => await ServiceProvider.CreateAsync())

or

Kernel.Bind<IServiceProvider>().ToMethod(async ctx => 
{
    var sp = new ServiceProvider();
    await sp.InitializeAsync();
})

Simple binding will work, but I'm not awaiting the result of asynchronous initialization started in constructor, as proposed by Stephen Cleary:

Kernel.Bind<IServiceProvider>().To<ServiceProvider>();

... and that's not looking good for me.

Homonym answered 9/4, 2013 at 16:24 Comment(2)
I don't think that's RAII. Probably the most important part is resource deallocation (though the name doesn't suggest that). And that doesn't really apply to C#, because GC causes non-deterministic deallocation.Adrianneadriano
Check out my libary AsyncContainerFiesole
E
55

I have a blog post that describes several approaches to async construction.

I recommend the asynchronous factory method as described by Reed, but sometimes that's not possible (e.g., dependency injection). In these cases, you can use an asynchronous initialization pattern like this:

public sealed class MyType
{
    public MyType()
    {
        Initialization = InitializeAsync();
    }

    public Task Initialization { get; private set; }

    private async Task InitializeAsync()
    {
        // Asynchronously initialize this instance.
        await Task.Delay(100);
    }
}

You can then construct the type normally, but keep in mind that construction only starts the asynchronous initialization. When you need the type to be initialized, your code can do:

await myTypeInstance.Initialization;

Note that if Initialization is already complete, execution (synchronously) continues past the await.


If you do want an actual asynchronous property, I have a blog post for that, too. Your situation sounds like it may benefit from AsyncLazy<T>:

public sealed class MyClass
{
    public MyClass()
    {
        MyProperty = new AsyncLazy<int>(async () =>
        {
            await Task.Delay(100);
            return 13;
        });
    }

    public AsyncLazy<int> MyProperty { get; private set; }
}
Elimination answered 9/4, 2013 at 16:37 Comment(0)
R
5

One potential option would be to move this to a factory method instead of using a constructor.

Your factory method could then return a Task<ServiceProvider>, which would allow you to perform the initialization asynchronously, but not return the constructed ServiceProvider until ImportantValue has been (asynchronously) computed.

This would allow your users to write code like:

var sp = await ServiceProvider.CreateAsync();
int iv = sp.ImportantValue; // Will be initialized at this point
Recovery answered 9/4, 2013 at 16:29 Comment(3)
Sorry, I forgot to mention in the question that my class has to implement an interface, and is intended to be used through interface only. This rules out the static factory method.Homonym
@Homonym In that case, you always will have a factory method somewhere (you can't construct an interface) - that makes this easier...Recovery
Do you have an example what the CreateAsync would look likePushcart
M
4

This is a slight modification to @StephenCleary pattern of async initialization.

The difference being the caller doesn't need to 'remember' to await the InitializationTask, or even know anything about the initializationTask (in fact it is now changed to private).

The way it works is that in every method that uses the initialized data there is an initial call to await _initializationTask. This returns instantly the second time around - because the _initializationTask object itself will have a boolean set (IsCompleted which the 'await' mechanism checks) - so don't worry about it initializing multiple times.

The only catch I'm aware of is you mustn't forget to call it in every method that uses the data.

public sealed class MyType
{
    public MyType()
    {
        _initializationTask = InitializeAsync();
    }

    private Task _initializationTask;

    private async Task InitializeAsync()
    {
        // Asynchronously initialize this instance.
        _customers = await LoadCustomersAsync();
    }

    public async Task<Customer> LookupCustomer(string name)
    {
         // Waits to ensure the class has been initialized properly
         // The task will only ever run once, triggered initially by the constructor
         // If the task failed this will raise an exception
         // Note: there are no () since this is not a method call
         await _initializationTask;

         return _customers[name];
    }

    // one way of clearing the cache
    public void ClearCache()
    {
         InitializeAsync();
    }

    // another approach to clearing the cache, will wait until complete
    // I don't really see a benefit to this method since any call using the
    // data (like LookupCustomer) will await the initialization anyway
    public async Task ClearCache2()
    {
         await InitializeAsync();
    }
 }
Millstream answered 6/12, 2017 at 21:53 Comment(4)
Wow, ok, I've been going about this all wrong, Thanks. How do you handle checking the result of that completed initialization?Lamentable
The important thing in my answer (which wasn't apparent to me at one point) is the realization that you can keep a reference to a task and that the actual task only runs once and whatever the result is is cached so whichever method you call down the chain will try to access the task's result and if there's an exception it will just sort of magically bubble up every time.Millstream
Ok, I ended up wrapping the awaits in a GuardInit() method, that awaits, but also checks the result if it was finished, allowing retry, or creating an application fail depending on the severity of the result. Thanks for the reply.Lamentable
Sure :) you get one long sentence when it's typed on my phone! I like the idea of auto retry - I suppose it depends what its doing.Millstream
F
2

You could use my AsyncContainer IoC container which supports the exact same scenario as you.

It also supports other handy scenarios such as async initializers, run-time conditional factories, depend on async and sync factory functions

//The email service factory is an async method
public static async Task<EmailService> EmailServiceFactory() 
{
  await Task.Delay(1000);
  return new EmailService();
}

class Service
{
     //Constructor dependencies will be solved asynchronously:
     public Service(IEmailService email)
     {
     }
} 

var container = new Container();
//Register an async factory:
container.Register<IEmailService>(EmailServiceFactory);

//Asynchronous GetInstance:
var service = await container.GetInstanceAsync<Service>();

//Safe synchronous, will fail if the solving path is not fully synchronous:
var service = container.GetInstance<Service>();
Fiesole answered 17/11, 2016 at 1:35 Comment(0)
B
0

I know this is an old question, but it's the first which appears on Google and, quite frankly, the accepted answer is a poor answer. You should never force a delay just so you can use the await operator.

A better approach to an initialization method:

private async Task<bool> InitializeAsync()
{
    try{
        // Initialize this instance.
    }

    catch{
        // Handle issues
        return await Task.FromResult(false);
    }

    return await Task.FromResult(true);
}

This will use the async framework to initialize your object, but then it will return a boolean value.

Why is this a better approach? First off, you're not forcing a delay in your code which IMHO totally defeats the purpose of using the async framework. Second, it's a good rule of thumb to return something from an async method. This way, you know if your async method actually worked/did what it was supposed to. Returning just Task is the equivalent of returning void on a non-async method.

Blah answered 2/5, 2016 at 21:59 Comment(2)
The await Task.Delay in my answer is a placeholder for "do the asynchronous work here to initialize the instance". There would be no Task.Delay in the actual code.Elimination
I feel that makes your answer worse since you failed to make it clear what you were doing and what you are showing is that you should be forcing delays to get async behavior during initialization/construction. You should keep in mind that anyone from all different levels of experience can see your post and you gave hardly any useful info in your answer and demonstrated poor coding without clarity. In other words, someone brand new to the async framework would see your answer and implement delays in their code to get async initialization/construction.Blah

© 2022 - 2024 — McMap. All rights reserved.