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

时间:2013-04-09 16:24:49

标签: c# design-patterns ninject async-await raii

我正在尝试在我的服务类中遵循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
    }
}

我的目标是摆脱ImportantValue getter中的副作用,使其成为线程安全的。

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

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

提前致谢。

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

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

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

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

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

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

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

5 个答案:

答案 0 :(得分:36)

我有一篇描述several approaches to async construction的博文。

我推荐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);
    }
}

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

await myTypeInstance.Initialization;

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


如果您确实需要实际asynchronous property, I have a blog post for that, too.您的情况听起来可能会从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; }
}

答案 1 :(得分:4)

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

然后,您的工厂方法可以返回Task<ServiceProvider>,这将允许您异步执行初始化,但不会返回构造的ServiceProvider,直到ImportantValue已经(异步)计算。 / p>

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

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

答案 2 :(得分:2)

您可以使用我的AsyncContainer IoC容器,它支持与您完全相同的方案。

它还支持其他方便的场景,例如异步初始化器,运行时条件工厂,依赖异步和同步工厂函数

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

答案 3 :(得分:2)

这是对@StephenCleary异步初始化模式的一点修改。

不同之处在于调用者无需“记住”await InitializationTask,甚至不知道有关initializationTask的任何信息(实际上现在已更改为私有) 。

它的工作方式是在使用初始化数据的每个方法中都有一个对await _initializationTask的初始调用。这会第二次立即返回 - 因为_initializationTask对象本身将有一个布尔集('{1}},'await'机制检查) - 所以不要担心它多次初始化。

我唯一知道的是你不能忘记在每个使用数据的方法中调用它。

IsCompleted

答案 4 :(得分:1)

我知道这是一个老问题,但它是第一个出现在Google上的问题,坦率地说,接受的答案是一个糟糕的答案。你永远不应该强迫延迟,以便你可以使用await运算符。

更好的初始化方法:

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

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

    return await Task.FromResult(true);
}

这将使用异步框架初始化您的对象,但随后它将返回一个布尔值。

为什么这是一种更好的方法?首先,你不会强迫你的代码延迟,恕我直言完全违背了使用异步框架的目的。其次,从异步方法返回一些东西是一个很好的经验法则。这样,您就知道您的异步方法是否真正起作用/做了它应该做的事情。返回Just Task相当于在非异步方法上返回void。