Durable Functions では、オーケストレーター関数で遅延を実装したり、非同期アクションのタイムアウトを設定したりするために使用する保証可能なタイマーが提供されます。 言語に組み込まれている可能性のある api を sleep または delay の代わりに、オーケストレーター関数で永続的タイマーを使用します。
Durable Task SDK は、遅延を実装したり、非同期アクションのタイムアウトを設定したりするために、オーケストレーションで使用する 永続的 タイマーを提供します。 言語に組み込まれている可能性がある API を sleep または delay の代わりに、オーケストレーションで永続的タイマーを使用します。
Important
現在、PowerShell Durable Task SDK は使用できません。
持続的タイマーは、次の例に示すように、指定された言語に適切な create timer API を使用して作成されたタスクであり、期限または期間を引数として使用します。
// Put the orchestrator to sleep for 72 hours
DateTime dueTime = context.CurrentUtcDateTime.AddHours(72);
await context.CreateTimer(dueTime, CancellationToken.None);
// Put the orchestrator to sleep for 72 hours
// Note that DateTime comes from the "luxon" module
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime, {zone: 'utc'}).plus({ hours: 72 });
yield context.df.createTimer(deadline.toJSDate());
# Put the orchestrator to sleep for 72 hours
due_time = context.current_utc_datetime + timedelta(hours=72)
durable_timeout_task = context.create_timer(due_time)
# Put the orchestrator to sleep for 72 hours
$duration = New-TimeSpan -Hours 72
Start-DurableTimer -Duration $duration
// Put the orchestrator to sleep for 72 hours
ctx.createTimer(Duration.ofHours(72)).await();
// Put the orchestration to sleep for 72 hours
await context.CreateTimer(TimeSpan.FromHours(72), CancellationToken.None);
# Put the orchestration to sleep for 72 hours
from datetime import timedelta
yield ctx.create_timer(timedelta(hours=72))
// Put the orchestration to sleep for 72 hours
ctx.createTimer(Duration.ofHours(72)).await();
// Put the orchestration to sleep for 72 hours
yield ctx.createTimer(72 * 60 * 60);
タイマー タスクを await すると、オーケストレーター関数は、指定された有効期限までスリープ状態になります。
タイマー タスクを await すると、オーケストレーションは指定された有効期限までスリープ状態になります。
注
オーケストレーションは、タイマー タスクの有効期限が切れるのを待っている間も、他の受信イベントを処理し続けます。
タイマーの制限事項
午後 4 時 30 分 (UTC) に有効期限が切れるタイマーを作成すると、基になる Durable Task Framework によって、午後 4 時 30 分 (UTC) にのみ表示されるメッセージがエンキューされます。 その間に関数アプリが 0 個のインスタンスにスケールダウンされた場合、新しく表示されるタイマー メッセージによって、関数アプリが適切な VM で再度アクティブ化されます。
注
- JavaScript、Python、PowerShell アプリの場合、持続的タイマーは 6 日間に制限されます。 この制限を回避するには、
while ループでタイマー API を使用して、より長い遅延をシミュレートします。 最新の.NETとJavaアプリでは、任意の長いタイマーがサポートされます。
- 使用されている SDK と ストレージ プロバイダー のバージョンによっては、必要な有効期限に達するまで、6 日以上の長いタイマーが一連の短いタイマー (たとえば、3 日間の期間) を使用して内部的に実装される場合があります。 この動作は基になるデータ ストアで監視できますが、オーケストレーションの動作には影響しません。
- 組み込みの日付/時刻 API を使用して現在の時刻を取得しないでください。 タイマーの有効期限が切れる将来の日付を計算するときは、常にオーケストレーター関数の現在の時刻 API を使用します。 詳細については、「オーケストレーター関数コードの制約」の記事を参照してください。
午後 4 時 30 分 (UTC) に有効期限が切れるタイマーを作成すると、基になる Durable Task Framework によって、午後 4 時 30 分 (UTC) にのみ表示されるメッセージがエンキューされます。 タイマー メッセージにより、タイマーの有効期限が切れたときにワーカーが再度アクティブになります。
注
- 長い遅延 (たとえば、数日以上の遅延) を指定すると、内部で管理される複数の永続的タイマーが作成される可能性があります。 オーケストレーション コードでは、この動作を認識する必要はありません。 ただし、フレームワーク ログと保存された履歴の状態に表示される場合があります。
- 組み込みの日時 API を使用して現在の時刻を取得しないでください。 タイマーの有効期限が切れる将来の日付を計算するときは、オーケストレーション コンテキストの現在の時刻プロパティ (.NET の
context.CurrentUtcDateTime、Python の ctx.current_utc_datetime、JavaScript の ctx.currentUtcDateTime など) を常に使用します。
遅延の使用方法
次の例は、持続的タイマーを使用して実行を遅延させる方法を示しています。 この例では、毎日 10 日間、課金通知を発行します。
[FunctionName("BillingIssuer")]
public static async Task Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
for (int i = 0; i < 10; i++)
{
DateTime deadline = context.CurrentUtcDateTime.Add(TimeSpan.FromDays(1));
await context.CreateTimer(deadline, CancellationToken.None);
await context.CallActivityAsync("SendBillingEvent");
}
}
注
上記の C# の例では、Durable Functions 2.x を対象とします。 Durable Functions 1.x の場合は、DurableOrchestrationContext の代わりに IDurableOrchestrationContext を使用します。 バージョン間の違いの詳細については、Durable Functions バージョンに関する記事を参照してください。
const df = require("durable-functions");
const { DateTime } = require("luxon");
module.exports = df.orchestrator(function*(context) {
for (let i = 0; i < 10; i++) {
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime, {zone: 'utc'}).plus({ days: 1 });
yield context.df.createTimer(deadline.toJSDate());
yield context.df.callActivity("SendBillingEvent");
}
});
import azure.functions as func
import azure.durable_functions as df
from datetime import datetime, timedelta
def orchestrator_function(context: df.DurableOrchestrationContext):
for i in range(0, 9):
deadline = context.current_utc_datetime + timedelta(days=1)
yield context.create_timer(deadline)
yield context.call_activity("SendBillingEvent")
main = df.Orchestrator.create(orchestrator_function)
param($Context)
for ($num = 0 ; $num -le 9 ; $num++){
$expiryTime = New-TimeSpan -Days 1
$timerTask = Start-DurableTimer -Duration $expiryTime
Invoke-DurableActivity -FunctionName 'SendBillingEvent'
}
@FunctionName("BillingIssuer")
public String billingIssuer(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.createTimer(Duration.ofDays(1)).await();
ctx.callActivity("SendBillingEvent").await();
}
return "done";
}
public class BillingIssuer : TaskOrchestrator<object?, string>
{
public override async Task<string> RunAsync(TaskOrchestrationContext context, object? input)
{
for (int i = 0; i < 10; i++)
{
await context.CreateTimer(TimeSpan.FromDays(1), CancellationToken.None);
await context.CallActivityAsync("SendBillingEvent");
}
return "done";
}
}
from datetime import timedelta
from durabletask import task
def send_billing_event(ctx: task.ActivityContext, _) -> None:
# Send billing event
pass
def billing_issuer(ctx: task.OrchestrationContext, _):
for i in range(10):
yield ctx.create_timer(timedelta(days=1))
yield ctx.call_activity(send_billing_event)
return "done"
public class BillingIssuer implements TaskOrchestration {
@Override
public void run(TaskOrchestrationContext ctx) {
for (int i = 0; i < 10; i++) {
ctx.createTimer(Duration.ofDays(1)).await();
ctx.callActivity("SendBillingEvent").await();
}
ctx.complete("done");
}
}
import { ActivityContext, OrchestrationContext, TOrchestrator } from "@microsoft/durabletask-js";
const sendBillingEvent = async (_: ActivityContext): Promise<void> => {
// Send billing event
};
const billingIssuer: TOrchestrator = async function* (ctx: OrchestrationContext): any {
for (let i = 0; i < 10; i++) {
yield ctx.createTimer(24 * 60 * 60); // 1 day
yield ctx.callActivity(sendBillingEvent);
}
return "done";
};
Warnung
オーケストレーター関数では無限ループを避けます。 無限ループ シナリオを安全かつ効率的に実装する方法については、「 永続的オーケストレーション」を参照してください。
Warnung
オーケストレーションで無限ループを回避します。 無限ループ シナリオを安全かつ効率的に実装する方法については、「 永続的オーケストレーション」を参照してください。
タイムアウトの使用
この例では、永続的タイマーを使用してタイムアウトを実装する方法を示します。
[FunctionName("TryGetQuote")]
public static async Task<bool> Run(
[OrchestrationTrigger] IDurableOrchestrationContext context)
{
TimeSpan timeout = TimeSpan.FromSeconds(30);
DateTime deadline = context.CurrentUtcDateTime.Add(timeout);
using (var cts = new CancellationTokenSource())
{
Task activityTask = context.CallActivityAsync("GetQuote");
Task timeoutTask = context.CreateTimer(deadline, cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
注
前の C# の例では、2.x Durable Functionsをターゲットにしています。 Durable Functions 1.x の場合は、DurableOrchestrationContext の代わりに IDurableOrchestrationContext を使用します。 バージョン間の違いの詳細については、Durable Functions バージョンに関する記事を参照してください。
const df = require("durable-functions");
const { DateTime } = require("luxon");
module.exports = df.orchestrator(function*(context) {
const deadline = DateTime.fromJSDate(context.df.currentUtcDateTime, {zone: 'utc'}).plus({ seconds: 30 });
const activityTask = context.df.callActivity("GetQuote");
const timeoutTask = context.df.createTimer(deadline.toJSDate());
const winner = yield context.df.Task.any([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
timeoutTask.cancel();
return true;
}
else
{
// timeout case
return false;
}
});
import azure.functions as func
import azure.durable_functions as df
from datetime import datetime, timedelta
def orchestrator_function(context: df.DurableOrchestrationContext):
deadline = context.current_utc_datetime + timedelta(seconds=30)
activity_task = context.call_activity("GetQuote")
timeout_task = context.create_timer(deadline)
winner = yield context.task_any([activity_task, timeout_task])
if winner == activity_task:
timeout_task.cancel()
return True
elif winner == timeout_task:
return False
main = df.Orchestrator.create(orchestrator_function)
param($Context)
$expiryTime = New-TimeSpan -Seconds 30
$activityTask = Invoke-DurableActivity -FunctionName 'GetQuote' -NoWait
$timerTask = Start-DurableTimer -Duration $expiryTime -NoWait
$winner = Wait-DurableTask -Task @($activityTask, $timerTask) -Any
if ($winner -eq $activityTask) {
Stop-DurableTimerTask -Task $timerTask
return $True
}
else {
return $False
}
@FunctionName("TryGetQuote")
public boolean tryGetQuote(
@DurableOrchestrationTrigger(name = "ctx") TaskOrchestrationContext ctx) {
Task<Double> activityTask = ctx.callActivity("GetQuote", Double.class);
Task<Void> timerTask = ctx.createTimer(Duration.ofSeconds(30));
Task<?> winner = ctx.anyOf(activityTask, timerTask);
if (winner == activityTask) {
// success case
return true;
} else {
// timeout case
return false;
}
}
public class TryGetQuote : TaskOrchestrator<object?, bool>
{
public override async Task<bool> RunAsync(TaskOrchestrationContext context, object? input)
{
using var cts = new CancellationTokenSource();
Task<double> activityTask = context.CallActivityAsync<double>("GetQuote");
Task timeoutTask = context.CreateTimer(TimeSpan.FromSeconds(30), cts.Token);
Task winner = await Task.WhenAny(activityTask, timeoutTask);
if (winner == activityTask)
{
// success case
cts.Cancel();
return true;
}
else
{
// timeout case
return false;
}
}
}
from datetime import timedelta
from durabletask import task
def get_quote(ctx: task.ActivityContext, _) -> float:
# Get quote logic
return 100.0
def try_get_quote(ctx: task.OrchestrationContext, _):
activity_task = ctx.call_activity(get_quote)
timeout_task = ctx.create_timer(timedelta(seconds=30))
winner = yield task.when_any([activity_task, timeout_task])
if winner == activity_task:
# success case
return True
else:
# timeout case
return False
public class TryGetQuote implements TaskOrchestration {
@Override
public void run(TaskOrchestrationContext ctx) {
Task<Double> activityTask = ctx.callActivity("GetQuote", Double.class);
Task<Void> timerTask = ctx.createTimer(Duration.ofSeconds(30));
Task<?> winner = ctx.anyOf(activityTask, timerTask).await();
if (winner == activityTask) {
// success case
ctx.complete(true);
} else {
// timeout case
ctx.complete(false);
}
}
}
import { ActivityContext, OrchestrationContext, TOrchestrator, whenAny } from "@microsoft/durabletask-js";
const getQuote = async (_: ActivityContext): Promise<number> => {
// Get quote logic
return 100.0;
};
const tryGetQuote: TOrchestrator = async function* (ctx: OrchestrationContext): any {
const activityTask = ctx.callActivity(getQuote);
const timeoutTask = ctx.createTimer(30);
const winner = yield whenAny([activityTask, timeoutTask]);
if (winner === activityTask) {
// success case
return true;
} else {
// timeout case
return false;
}
};
Warnung
.NET、JavaScript、Python、および PowerShell では、作成した永続的タイマーが完了するのをコードが待たない場合、それらのタイマーをキャンセルしてください。 保留中のタイマーを取り消す方法については、前の例を参照してください。 Durable Task Framework は、持続的タイマー タスクを含むすべての未処理のタスクが完了または取り消されるまで、オーケストレーションの状態を "完了" に変更しません。
Warnung
SDK でタイマーの取り消し (.NET など) がサポートされている場合は、作成された永続的タイマーが完了するまでコードが待機しない場合はキャンセルします。 保留中のタイマーを取り消す方法については、前の例を参照してください。 Durable Task Framework は、持続的タイマー タスクを含むすべての未処理のタスクが完了または取り消されるまで、オーケストレーションの状態を "完了" に変更しません。
when-any パターンを使用するこのキャンセル メカニズムは、進行中のアクティビティ関数やサブオーケストレーションの実行を終了しません。 代わりに、オーケストレーター関数が結果を無視して次に進むだけです。 関数アプリで従量課金プランが使用されている場合でも、破棄されたアクティビティ関数が消費する時間とメモリに対して課金されます。 既定では、従量課金プランで実行されている関数のタイムアウトは 5 分です。 この制限を超えた場合、Azure Functions ホストはリサイクルしてすべての実行を停止し、課金が暴走するのを防ぎます。
関数のタイムアウトは構成可能です。
オーケストレーター関数にタイムアウトを実装する方法の詳細な例については、 人間の操作 に関する記事を参照してください。
when-any パターンを使用するこのキャンセル メカニズムは、進行中のアクティビティまたはサブオーケストレーションの実行を終了しません。 オーケストレーションが単に結果を無視して次のステップに進むようにします。
次のステップ