如何使用async-await模式初始化对象

Has*_*tor 16 c# design-patterns ninject raii async-await

我试图在我的服务类中遵循RAII模式,这意味着当构造一个对象时,它会被完全初始化.但是,我遇到了异步API的困难.有问题的类的结构如下所示

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
    }
}
Run Code Online (Sandbox Code Playgroud)

我也打算在ImportantValuegetter中摆脱副作用,使其成为线程安全的.

现在,用户ServiceProvider将创建它的实例,订阅ImportantValue更改事件,并获得初始值ImportantValue.这里出现了问题,初始值.由于ImportantValue是异步计算的,因此无法在构造函数中完全初始化类.最初将此值设为null可能没问题,但是我需要在某个地方首次计算它.一个自然的地方可能是ImportantValue吸气剂,但我的目标是使其线程安全,没有副作用.

所以我基本上坚持这些矛盾.你能帮助我并提供一些替代方案吗?在构造函数中初始化值虽然不是很好,但是没有副作用和属性的线程安全是必需的.

提前致谢.

编辑:还有一件事要补充.我正在使用Ninject进行实例化,据我所知,它不支持异步方法来创建绑定.虽然在构造函数中启动一些基于任务的操作的方法将起作用,但我无法等待其结果.

即两个下一个方法(目前为止提供的答案)将无法编译,因为返回了Task,而不是我的对象:

Kernel.Bind<IServiceProvider>().ToMethod(async ctx => await ServiceProvider.CreateAsync())
Run Code Online (Sandbox Code Playgroud)

要么

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

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

Kernel.Bind<IServiceProvider>().To<ServiceProvider>();
Run Code Online (Sandbox Code Playgroud)

......这对我来说并不好看.

Ste*_*ary 39

我有一篇博客文章介绍了几种async构建方法.

我推荐Reed所描述的异步工厂方法,但有时这是不可能的(例如,依赖注入).在这些情况下,您可以使用如下的异步初始化模式:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以正常构造类型,但请记住,构造仅启动异步初始化.当您需要初始化类型时,您的代码可以执行以下操作:

await myTypeInstance.Initialization;
Run Code Online (Sandbox Code Playgroud)

请注意,如果Initialization已经完成,则执行(同步)继续经过await.


如果你确实想要一个实际的异步属性,我也有一个博客文章.您的情况听起来可能会受益于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; }
}
Run Code Online (Sandbox Code Playgroud)


Ree*_*sey 5

一种可能的选择是将其移动到工厂方法而不是使用构造函数。

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

这将允许您的用户编写如下代码:

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