22

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

我的目标还在于消除ImportantValuegetter 中的副作用,使其成为线程安全的。

现在 的用户ServiceProvider将创建它的一个实例,订阅一个ImportantValue更改事件,并获取初始的ImportantValue. 问题来了,初始值。由于ImportantValue是异步计算的,因此无法在构造函数中完全初始化该类。最初将此值设置为 null 可能没问题,但随后我需要有一些地方可以第一次计算它。一个自然的地方可能是ImportantValue'getter,但我的目标是使其成为线程安全且没有副作用的。

所以我基本上被这些矛盾所困扰。你能帮助我并提供一些替代方案吗?在构造函数中初始化值虽然 nice 并不是真正必要的,但没有副作用和属性的线程安全是强制性的。

提前致谢。

编辑:还要添加一件事。我正在使用 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>();

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

4

5 回答 5

49

我有一篇博客文章描述了几种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);
    }
}

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

await myTypeInstance.Initialization;

请注意,如果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; }
}
于 2013-04-09T16:37:39.137 回答
5

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

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

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

var sp = await ServiceProvider.CreateAsync();
int iv = sp.ImportantValue; // Will be initialized at this point
于 2013-04-09T16:29:52.373 回答
3

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

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

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

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

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<Customer> 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();
    }
 }
于 2017-12-06T21:53:58.103 回答
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>();
于 2016-11-17T01:35:23.107 回答
0

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

初始化方法的更好方法:

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

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

    return await Task.FromResult(true);
}

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

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

于 2016-05-02T21:59:28.650 回答