我正在尝试在我的服务类中遵循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>();
......这对我来说并不好看。
答案 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。