モバイルバッテリーの電力移動効率を調べてみた

タブレットの電池を消費した状態から充電開始。モバイルバッテリーが力尽きるまで充電し、増加分を計測します。
※厳密な測定ではないので、参考程度にお考えください。

受電側

本体バッテリー 8400mAh

給電側

① RAVPower モバイルバッテリー 10000mAh

結果

15% → 85%
= +70%
= +5880mAh
58.8%

② Anker PowerCore 10000 PD Redux

結果

11% → 88%
= +77%
= +6468mAh
64.68%

Azure Functions を DI ベースに構成するテンプレ

Azure Functions を DI ベースに構成します。
.NET Core で環境変数や設定値の読み込みが、なんだか面倒になったところも解消します。

DI 構成

以下の NuGet パッケージを利用します。 www.nuget.org

FunctionsStartup を構成します。
DI の代表的存在であろう HttpClientFactory を組み込んでみます。

/* Startup.cs */
using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            // HttpClientFactory
            builder.Services.AddHttpClient();

            // Scope 実験のためのオブジェクト(後述)
            builder.Services.AddScoped<DisposableObject>();

            // 環境変数の読み込み(後述)
            builder.Services.AddOptions<FunctionHttpOptions>()
                   .Configure<IConfiguration>(
                       (options, config) =>
                       {
                           config.GetSection("MyOptions").Bind(options);
                       });
        }
    }
}

環境変数、設定値

GenericHost に準拠した構成なので IOptions を利用できます。 ただ、Azure Functions の実行環境は、標準的な .NET Core App で行われる構造化された JSON ファイルとバインドするような形式ではありません。(実行環境に設定ファイルを配置しません。)

Azure Portal での設定はこのようになります。 f:id:HollowCat:20210602170834p:plain

開発環境の local.settings.json での記述は以下のようになります。

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=...",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "MyOptions:Setting1": "Value1",
    "MyOptions:Setting2": "Value2"
  }
}

IOptions<T> で変数を受け取る型は以下のように定義します。

public class FunctionHttpOptions
{
    public string Setting1 { get; set; }
    public string Setting2 { get; set; }
}

 
このような設定値が Startup.Configure() 内の記述により IOptions<FunctionHttpOptions> にバインドされます。
.GetSection("MyOptions") ではプレフィックスで値を選別しています。

HttpTrigger

DI するために class から static は外しておきます。

public class FunctionHttp
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly IConfiguration _configuration;
    private readonly IOptions<FunctionHttpOptions> _options;
    private readonly DisposableObject _disposableObject;

    public FunctionHttp(
        IHttpClientFactory httpClientFactory,
        IConfiguration configuration,  // IConfiguration で設定値すべてにアクセス
        IOptions<FunctionHttpOptions> options,
        DisposableObject disposableObject)
    {
        _httpClientFactory = httpClientFactory;
        _configuration = configuration;
        _options = options;
        _disposableObject = disposableObject;

        Debug.WriteLine($"FunctionHttp");
    }

    [FunctionName("FunctionHttp")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Function, "get", Route = null)] HttpRequest request,
        [Queue("myqueue-items", Connection = "AzureWebJobsStorage")] CloudQueue queue,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");

        // 設定値の利用
        log.LogInformation($"FunctionHttp");
        log.LogInformation($"  Setting1={_options?.Value.Setting1}");
        log.LogInformation($"  Setting2={_options?.Value.Setting2}");
        log.LogInformation($"  AzureWebJobsStorage={_configuration["AzureWebJobsStorage"]}");

        string name = request.Query["name"];
        var responseMessage = string.IsNullOrEmpty(name)
            ? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
            : $"Hello, {name}. This HTTP triggered function executed successfully.";

        // QueueTrigger 起動のためにメッセージを送る (後述)
        await queue.AddMessageAsync(new CloudQueueMessage(responseMessage));

        return new OkObjectResult(responseMessage);
    }
}

QueueTrigger

QueueTrigger でもちゃんと DI を使えます。 static を外しておくだけです。

public class FunctionQueue
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly DisposableObject _disposableObject;


    public FunctionQueue(
        IHttpClientFactory httpClientFactory,
        DisposableObject disposableObject)
    {
        _httpClientFactory = httpClientFactory;
        _disposableObject = disposableObject;

        Debug.WriteLine($"FunctionQueue");
    }

    [FunctionName("FunctionQueue")]
    public void Run(
        [QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string myQueueItem,
        ILogger log)
    {
        log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
    }
}

Scope

DI コンテナのスコープ管理も利用されています。挙動を見るために DisposableObject を スコープ内で共有されるように仕込んでいました。 実装は以下の様に簡単なものです。

public class DisposableObject : IDisposable
{
    private readonly Guid  _id = Guid.NewGuid();


    public DisposableObject()
    {
        Debug.WriteLine($"Instantiate DisposableImpl: {_id}");
    }

    public void Dispose()
    {
        Debug.WriteLine($"Dispose DisposableImpl: {_id}");
    }
}

ログ出力は

Instantiate DisposableImpl: 2344ec50-c701-481a-9297-230e5ac3b201
FunctionHttp
  Setting1=Value1
  Setting2=Value2
Dispose DisposableImpl: 2344ec50-c701-481a-9297-230e5ac3b201
...
...
...
Instantiate DisposableImpl: c3a4c3eb-1e90-4cb2-a8aa-6d584528003c
FunctionQueue
  Setting1=Value1
  Setting2=Value2
Dispose DisposableImpl: c3a4c3eb-1e90-4cb2-a8aa-6d584528003c

となっており、メソッド実行毎にスコープが分けられている事が確認できます。

検証環境

  • Azure Functions v3
  • .NET Core 3.1

参考リンク

GitHub - shibayan/azure-functions-http-api: HTTP API Extensions for Azure Functions v3
.NET Azure Functions で依存関係の挿入を使用する | Microsoft Docs
ASP.NET Core のオプション パターン | Microsoft Docs