如何使用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 } } 

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

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

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

提前致谢。

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

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

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

要么

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

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

 Kernel.Bind().To(); 

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

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

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

 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


如果你确实想要一个实际的异步属性,我也有一个博客文章。 您的情况听起来可能会受益于AsyncLazy

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

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

然后,您的工厂方法可以返回Task ,它允许您异步执行初始化,但不会返回构造的ServiceProvider直到(异步)计算了ImportantValue

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

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

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

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

 //The email service factory is an async method public static async Task 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(EmailServiceFactory); //Asynchronous GetInstance: var service = await container.GetInstanceAsync(); //Safe synchronous, will fail if the solving path is not fully synchronous: var service = container.GetInstance(); 

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

区别在于调用者不需要“记住” await InitializationTask ,甚至不知道有关initializationTask任何信息(实际上它现在已更改为private)。

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

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

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

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

更好的初始化方法:

 private async Task InitializeAsync() { try{ // Initialize this instance. } catch{ // Handle issues return await Task.FromResult(false); } return await Task.FromResult(true); } 

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

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