У меня есть существующее приложение-функция с 2 функциями и очередью хранения. F1 запускается сообщением в теме служебной шины. Для каждого полученного сообщения F1 вычисляет несколько подзадач (T1, T2, ...), которые должны выполняться с различной задержкой. Пример - T1 запускается через 3 минуты, T2 через 5 минут и т. Д. F1 отправляет сообщения в очередь хранения с соответствующими таймаутами видимости (для имитации задержки), а F2 запускается всякий раз, когда сообщение появляется в очереди. Все работает нормально.
Теперь я хочу перенести это приложение на использование «долговечных функций». F1 теперь только запускает оркестратор. Код оркестратора выглядит следующим образом:
public static async Task Orchestrator([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log)
{
var results = await context.CallActivityAsync<List<TaskInfo>>("CalculateTasks", "someinput");
List<Task> tasks = new List<Task>();
foreach (var value in results)
{
var pnTask = context.CallActivityAsync("PerformSubTask", value);
tasks.Add(pnTask);
}
//dont't await as we want to fire and forget. No fan-in!
//await Task.WhenAll(tasks);
}
[FunctionName("PerformSubTask")]
public async static Task Run([ActivityTrigger]TaskInfo info, TraceWriter log)
{
TimeSpan timeDifference = DateTime.UtcNow - info.Origin.ToUniversalTime();
TimeSpan delay = TimeSpan.FromSeconds(info.DelayInSeconds);
var actualDelay = timeDifference > delay ? TimeSpan.Zero : delay - timeDifference;
//will still keep the activity function running and incur costs??
await Task.Delay(actualDelay);
//perform subtask work after delay!
}
Я хотел бы только разветвляться (без разветвления для сбора результатов) и запускать подзадачи. Оркестратор запускает все задачи и избегает вызова await Task.WhenAll. Функция активности вызывает Task.Delay для ожидания указанного количества времени, а затем выполняет свою работу.
Мои вопросы
Имеет ли смысл использовать устойчивые функции для этого рабочего процесса?
Это правильный подход к организации рабочего процесса «разветвления»?
Мне не нравится, что функция активности выполняется в течение определенного времени (3 или 5 минут), ничего не делая. Это повлечет за собой расходы, или?
Также, если требуется задержка более 10 минут, существует ни в коем случае для функции активности при таком подходе!
Моя предыдущая попытка избежать этого заключалась в том, чтобы использовать CreateTimer в оркестраторе, а затем добавить действие в качестве продолжения, но я вижу только записи таймера в таблице «History». Продолжение не запускается! Нарушаю ли я ограничение для кода оркестратора - 'Код оркестратора никогда не должен инициировать какие-либо асинхронные операции'?
foreach (var value in results) { //calculate time to start var timeToStart = ; var pnTask = context.CreateTimer(timeToStart , CancellationToken.None).ContinueWith(t => context.CallActivityAsync("PerformSubTask", value)); tasks.Add(pnTask); }
ОБНОВЛЕНИЕ: с использованием подхода, предложенного Крисом
Деятельность, рассчитывающая подзадачи и задержки
[FunctionName("CalculateTasks")]
public static List<TaskInfo> CalculateTasks([ActivityTrigger]string input,TraceWriter log)
{
//in reality time is obtained by calling an endpoint
DateTime currentTime = DateTime.UtcNow;
return new List<TaskInfo> {
new TaskInfo{ DelayInSeconds = 10, Origin = currentTime },
new TaskInfo{ DelayInSeconds = 20, Origin = currentTime },
new TaskInfo{ DelayInSeconds = 30, Origin = currentTime },
};
}
public static async Task Orchestrator([OrchestrationTrigger] DurableOrchestrationContext context, TraceWriter log)
{
var results = await context.CallActivityAsync<List<TaskInfo>>("CalculateTasks", "someinput");
var currentTime = context.CurrentUtcDateTime;
List<Task> tasks = new List<Task>();
foreach (var value in results)
{
TimeSpan timeDifference = currentTime - value.Origin;
TimeSpan delay = TimeSpan.FromSeconds(value.DelayInSeconds);
var actualDelay = timeDifference > delay ? TimeSpan.Zero : delay - timeDifference;
var timeToStart = currentTime.Add(actualDelay);
Task delayedActivityCall = context
.CreateTimer(timeToStart, CancellationToken.None)
.ContinueWith(t => context.CallActivityAsync("PerformSubtask", value));
tasks.Add(delayedActivityCall);
}
await Task.WhenAll(tasks);
}
Кажется, работает простое планирование задач из оркестратора. В моем случае я вычисляю задачи и задержки в другом действии (CalculateTasks) до цикла. Я хочу, чтобы задержки рассчитывались с использованием «текущего времени», когда выполнялось действие. Я использую DateTime.UtcNow в действии. Это почему-то не работает при использовании в оркестраторе. Действия, указанные в ContinueWith, просто не выполняются, а оркестратор всегда находится в состоянии «Выполняется».
Могу ли я не использовать время, зарегистрированное действием в оркестраторе?
ОБНОВЛЕНИЕ 2
Так что обходной путь, предложенный Крисом, работает!
Поскольку я не хочу собирать результаты действий, я избегаю вызова «await Tasks.WhenAll(tasks)
» после планирования всех действий. Я делаю это, чтобы уменьшить конкуренцию в очереди управления, то есть иметь возможность запустить другую оркестровку, если требуется. Тем не менее, статус «оркестратора» по-прежнему «Выполняется», пока все действия не завершатся. Я предполагаю, что он переместится в «Complete» только после того, как последнее действие отправит сообщение «готово» в очередь управления.
Я прав? Есть ли способ освободить оркестратор раньше, т.е. сразу после планирования всех действий?