Azure Functions:用于昂贵对象的Singleton

时间:2017-09-11 18:26:30

标签: .net azure azure-functions

我创建了一些非常简单的Azure功能。他们从Couchbase(在VM上运行Azure)中读取和写入数据。

我关注我在Azure功能中与Couchbase建立的连接。我每次都创建一个Cluster对象。这是一项昂贵的操作,我通常只会在普通的网络应用程序中执行一次。但是在Azure功能中,我每次都会new

除了Couchbase之外,实例化这样的对象还有很多代价。有没有办法创建一个单例,或Azure函数可以在调用之间重用的某种共享对象?

4 个答案:

答案 0 :(得分:15)

在Azure Functions上处理单例时,有几个注意事项。一个是AF调用之间共享全局状态 。因此,如果一个函数被调用一次然后再调用(很快主机没有卸载你的代码),那么初始化只发生一次。另一个考虑因素是AF可以完全自由地同时启动多个AF调用 - 因此任何单例都需要线程安全(包括初始化)。

这意味着您需要使用Lazy<T> / AsyncLazy<T>。但是,请记住AF(使用这些类型)将保留下一次调用的单例状态(后初始化),即使它失败。这可能是一个问题,尤其是云计算问题,因为如果AF启动时出现网络(或配置)错误,您希望在下一次AF调用时重试初始化。

总之,您希望以线程安全不会保留失败的方式使用Lazy<T> / AsyncLazy<T>

使用Lazy<T>,这意味着you have to use the LazyThreadSafetyMode.PublicationOnly flag 将一个函数传递给构造函数(不只是隐式使用T的默认构造函数)。请注意,这意味着您需要确保初始化函数本身是线程安全的,因为它可以由多个线程同时执行。

AsyncLazy<T>you have to use the AsyncLazyFlags.RetryOnFailure flag。由于AsyncLazy<T>本质上是Lazy<Task<T>>,因此异步初始化任务在所有同时调用者之间“共享”,然后如果失败则以新的Lazy<Task<T>>实例原子替换。因此,异步初始化函数不需要是线程安全的。

由于这一切都很好(特别是对于多个单身人士)是相当复制和粘贴的,我将其抽象为the AF project I'm working on

这需要一段时间才能达到这一点,但我对它的结果非常满意。对博客也有意义......

答案 1 :(得分:9)

昂贵的连接对象的静态属性可以正常工作,但我建议将它们包装在Lazy<>中,这样您就能获得开箱即用的线程安全保障。

基于示例博客文章,您链接到一个示例,该示例使得以可保证的线程安全方式在所有函数调用中重用该存储区可能看起来像这样:

public class FunctionClass
{
    private static Lazy<IBucket> LazyBucket = new Lazy<IBucket>(() =>
    {
        var uri = ConfigurationManager.AppSettings["couchbaseUri"];
        var cluster = new Cluster(new ClientConfiguration
        {
            Servers = new List<Uri> { new Uri(uri) }
        });

        var bucketName = ConfigurationManager.AppSettings["couchbaseBucketName"];
        var bucketPassword = ConfigurationManager.AppSettings["couchbaseBucketPassword"];

        return cluster.OpenBucket(bucketName, bucketPassword);
    });

    // Your actual function implementation
    public static async Task Run()
    {
        // Here you are guaranteed to get back a shared connection object to your bucket that has been
        // initalized only once in a thread safe way
        var initalizedOnceBucket = LazyBucket.Value;

        // do something with the bucket
    }
}

如果构建应该共享的昂贵对象依赖于某些异步调用(我怀疑Couchbase C#客户端可能具有异步版本的方法)。您可以使用Stephen Cleary编写的令人敬畏的Nito.AsyncEx Nuget包中的AsyncLazy<>。常规Lazy<>内置于.NET中,因此不需要任何外部依赖项。

答案 2 :(得分:5)

您可以使用普通单例,即返回某个实例的静态属性。与往常一样,请注意线程安全性,例如使用Lazy<T>作为@Jesse建议。

在第一次调用Function之前,您还可以使用静态构造函数进行初始化。根据定义,静态构造函数是线程安全的。

在这两种情况下,您都可以在同一个实例(服务器)上运行的所有调用之间重用昂贵的东西。

答案 3 :(得分:1)

您可以在Azure函数本身上使用[Singleton]属性,以确保例如同步调用队列函数。但是,如果线程随后进入睡眠状态,则可以读取下一个队列项目。因此,我使用了此代码,并在代码中围绕昂贵的调用实现了单例模式,以确保关键代码一次只能执行一次。

例如

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div data-bind="foreach: items">
  <label>
   <input type="checkbox" data-bind="checkedValue: $data, checked: selected">
   <span data-bind="text: name"></span>
  </label> 
</div>

单一模式:

[Singleton(Mode = SingletonMode.Function)]
[FunctionName("TimerFunction")]