如何使用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。
- 在Task.WhenAll中完成任务时是否有回调
- 异步/等待.NET的WebBrowser类的实现
- .net框架是否提供了使用文件系统的异步方法?
- await / async Microsoft Practices企业库数据
- AsyncCTP:创建一个IAwaitable类
- FromBluetoothAddressAsync IAsyncOperation不包含“GetAwaiter”错误的定义
- 我正在尝试将同步方法从一些旧代码转换为异步方法,但我有一些麻烦
- 为什么在不使用async / await关键字时异步unit testing失败?
- RegAsm无法使用Microsoft.Bcl.Async进行.NET 4.0程序集