简体   繁体   English

如何使用异步等待模式初始化对象

[英]How to initialize an object using async-await pattern

I'm trying to follow RAII pattern in my service classes, meaning that when an object is constructed, it is fully initialized.我试图在我的服务类中遵循 RAII 模式,这意味着当一个对象被构造时,它会被完全初始化。 However, I'm facing difficulties with asynchronous APIs.但是,我在使用异步 API 时遇到了困难。 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.我的目标还在于消除ImportantValue getter 中的副作用,使其成为线程安全的。

Now users of ServiceProvider will create an instance of it, subscribe to an event of ImportantValue change, and get the initial ImportantValue .现在ServiceProvider的用户将创建它的一个实例,订阅一个ImportantValue值更改的事件,并获得初始的ImportantValue值。 And here comes the problem, with the initial value.问题来了,初始值。 Since the ImportantValue is calculated asynchronously, the class cannot be fully initialized in constructor.由于ImportantValue是异步计算的,因此无法在构造函数中完全初始化该类。 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.最初将此值设置为 null 可能没问题,但随后我需要有一些地方可以第一次计算它。 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.一个自然的地方可能是ImportantValue的吸气剂,但我的目标是使其成为线程安全且没有副作用的。

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.在构造函数中初始化值虽然 nice 并不是真正必要的,但没有副作用和属性的线程安全是强制性的。

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.我正在使用 Ninject 进行实例化,据我了解,它不支持异步方法来创建绑定。 While approach with initiating some Task-based operation in constructor will work, I cannot await its result.虽然在构造函数中启动一些基于任务的操作的方法会起作用,但我不能等待它的结果。

Ie two next approaches (offered as answers so far) will not compile, since Task is returned, not my object:即两种下一个方法(到目前为止作为答案提供)将无法编译,因为返回的是 Task,而不是我的对象:

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:简单的绑定将起作用,但我不等待在构造函数中启动的异步初始化的结果,正如 Stephen Cleary 所提出的:

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

... and that's not looking good for me. ......这对我来说并不好。

I have a blog post that describes several approaches to async construction .我有一篇博文,描述async构造的几种方法

I recommend the asynchronous factory method as described by Reed, but sometimes that's not possible (eg, dependency injection).我推荐 Reed 描述的异步工厂方法,但有时这是不可能的(例如,依赖注入)。 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 .请注意,如果Initialization已经完成,则执行(同步)会在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> :您的情况听起来可能会受益于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; }
}

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.然后,您的工厂方法可以返回Task<ServiceProvider> ,这将允许您异步执行初始化,但在(异步)计算ImportantValue值之前不会返回构造的ServiceProvider

This would allow your users to write code like:这将允许您的用户编写如下代码:

var sp = await ServiceProvider.CreateAsync();
int iv = sp.ImportantValue; // Will be initialized at this point

This is a slight modification to @StephenCleary pattern of async initialization.这是对异步初始化的@StephenCleary 模式的轻微修改。

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).不同之处在于调用者不需要“记住” await InitializationTask ,甚至不需要了解有关initializationTask的任何信息(实际上它现在已更改为私有)。

The way it works is that in every method that uses the initialized data there is an initial call to await _initializationTask .它的工作方式是在每个使用初始化数据的方法中都有一个对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.这第二次立即返回 - 因为_initializationTask对象本身将有一个布尔集( IsCompleted ,“等待”机制检查) - 所以不要担心它会多次初始化。

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

You could use my AsyncContainer IoC container which supports the exact same scenario as you.您可以使用我的AsyncContainer IoC 容器,它支持与您完全相同的场景。

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

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.我知道这是一个老问题,但它是第一个出现在 Google 上的问题,坦率地说,接受的答案是一个糟糕的答案。 You should never force a delay just so you can use the await operator.您永远不应该强制延迟,以便您可以使用 await 运算符。

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.仅返回 Task 相当于在非异步方法上返回 void。

I have a variation of Stephen Cleary's example of an asynchronous initialization pattern.我有 Stephen Cleary 的异步初始化模式示例的变体。 You could encapsulate the Initialization property and await it in the class methods.您可以封装 Initialization 属性并在类方法中等待它。 In this case, the client code will not need to await the initialization task.在这种情况下,客户端代码将不需要等待初始化任务。

public class ClassWithAsyncInit
{
    public ClassWithAsyncInit()
    {
        Initialization = InitializeAsync();
    }

    private Task Initialization { get; private set; }

    private async Task InitializeAsync()
    {
    // your async init code
    }

    public async Task FirstMethod()
    {
        await Initialization;
        // ... other code
    }
}

The drawback is that it's not convenient if you have a lot of methods in your class and need to await the Initialization task in each one.缺点是如果您的类中有很多方法并且需要等待每个方法中的初始化任务,则不方便。 But sometimes it is okay.但有时没关系。 Let's say you have a simple interface for saving some JSON objects:假设您有一个简单的接口来保存一些 JSON 对象:

public IDataSaver
{
    void Save(string json);
}

And you need to implement it for a database with the asynchronous initialization logic.您需要为具有异步初始化逻辑的数据库实现它。 Considering that you would have only one public method it makes sense to encapsulate the Initialization property and await it in the Save method:考虑到您只有一个公共方法,封装 Initialization 属性并在 Save 方法中等待它是有意义的:

public class SomeDbDataSaver: IDataSaver
{
    protected DatabaseClient DbClient { get; set; }

    public SomeDbDataSaver()
    {
        DbClient = new DatabaseClient();
        Initialization = InitializeAsync(); // start off the async init
    }

    private Task Initialization { get; private set; }

    private async Task InitializeAsync()
    {
        await DbClient.CreateDatabaseIfNotExistsAsync("DatabaseName");
    }

    public async Task Save(string json)
    {
        await Initialization;
        
        // ... code for saving a data item to the database
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM