🎉
Durable Functions で ContinueAsNew を使っているオーケストレーター関数の戻り値
Durable Functions で、そこそこ長い処理をする可能性があるオーケストレーター関数では適宜 ContinueAsNew
を使って新しいインプットと共に自分自身を再起動することがあります。この場合、オーケストレーター関数の戻り値はどうなるのか気になったので調べてみました。
Durable Functions を新規作成したプロジェクトでおなじみの SayHello
を 3 都市に対して呼び出すオーケストレーター関数を以下のように書き換えてみました。
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.Extensions.Logging;
using System.Runtime.InteropServices;
namespace FunctionApp1
{
public static class Function1
{
[Function(nameof(Function1))]
public static async Task<string> RunOrchestrator(
[OrchestrationTrigger] TaskOrchestrationContext context)
{
ILogger logger = context.CreateReplaySafeLogger(nameof(Function1));
// 引数で受け取ったリストの最初の要素に対して SayHello アクティビティを呼び出す
var input = context.GetInput<string[]>();
ArgumentNullException.ThrowIfNull(input);
if (input.Length == 0) throw new ArgumentException("Please pass a list with at least one item in it", nameof(input));
var message = await context.CallActivityAsync<string>(nameof(SayHello), input[0]);
logger.LogInformation(message);
// まだ引数に後続の値がある場合は ContinueAsNew を呼び出して再実行
// そうじゃない場合は終了
if (input.Length == 1)
{
return "Done!!";
}
else
{
context.ContinueAsNew(input[1..]);
return "Continuing...";
}
}
[Function(nameof(SayHello))]
public static string SayHello([ActivityTrigger] string name, FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("SayHello");
logger.LogInformation("Saying hello to {name}.", name);
return $"Hello {name}!";
}
[Function("Function1_HttpStart")]
public static async Task<HttpResponseData> HttpStart(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext executionContext)
{
ILogger logger = executionContext.GetLogger("Function1_HttpStart");
// Tokyo, Seattle, London を渡してオーケストレーター関数を起動
var instanceId = await client.ScheduleNewOrchestrationInstanceAsync(
nameof(Function1),
input: (string[])["Tokyo", "Seattle", "London"]);
logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId);
// オーケストレーター関数の完了を待機
var metadata = await client.WaitForInstanceCompletionAsync(instanceId, getInputsAndOutputs: true);
// 結果を返す
var res = HttpResponseData.CreateResponse(req);
await res.WriteAsJsonAsync(metadata);
return res;
}
}
}
RunOrchestrator
メソッドのコメントに書いてある通り ContinueAsNew
を使うように変更しています。ContinueAsNew
を呼び出す時には Continuing...
という文字列を返して、本当に終了する時には Done!!
という文字列を返すようにしています。
HttpStarter
メソッドではオーケストレーター関数を起動したあとに完了を待って、その結果を JSON で返すようにしています。
この状態で実行してみると、以下のような結果が返ってきました。
{
"Name": "Function1",
"InstanceId": "8d46ce370f9349b5ad665b9ec10b77bc",
"DataConverter": {},
"RuntimeStatus": 1,
"CreatedAt": "2024-10-10T01:35:02.5404041+00:00",
"LastUpdatedAt": "2024-10-10T01:35:02.6202305+00:00",
"SerializedInput": "[\"London\"]",
"SerializedOutput": "\"Done!!\"",
"SerializedCustomStatus": "null",
"FailureDetails": null,
"IsRunning": false,
"IsCompleted": true
}
SerializedOutput
にオーケストレーター関数の戻り値が入るのですが、ちゃんと終了した時に返される Done!!
が入っていました。ということは ContinueAsNew
を呼んだケースでの戻り値は無視されるようです。
まとめ
ContinueAsNew
を呼ぶオーケストレーター関数で戻り値を返す際には、ダミーの戻り値を返すので OK。ContinueAsNew
を呼ばないルートの本当に終了するケースでの戻り値が最終的なアウトプットとして扱われる。
Discussion