隔离模式下Durable子编排函数无法接收外部事件的问题
Durable子编排无法接收外部事件的问题
我在Durable子编排函数中等待外部事件时遇到了问题:发送给主编排实例ID的事件能被主编排正常接收,但子编排完全收不到。
我知道问题出在事件是发给主编排实例ID的,但现在的麻烦是没法便捷获取子编排的实例ID。在隔离模式下,TaskOrchestrationContext没有直接提供获取子编排实例ID的方法;如果让子编排启动后把自己的ID回传给主编排,这种实现方式既繁琐又不合理。
相关代码
以下是完整代码,Jobs_HttpStart_Event用于启动编排并触发事件:
using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Http; using Microsoft.DurableTask; using Microsoft.DurableTask.Client; using Microsoft.Extensions.Logging; namespace DurableFuncExperiment { public static class Function { public record EventData(string instanceId, string eventName, string data); public const string SayHelloOrchestrator = "Jobs_SayHello"; public const string SayHelloOneActivitity = "Jobs_SayHelloOne"; public const string SayHelloEventOrchestrator = "Jobs_SayHelloEventOrchestrator"; public const string SayHelloEventSubOrchestrator = "Jobs_SayHelloEventSubOrchestrator"; // Leave SayHello for experiments if needed [Function(SayHelloOrchestrator)] public static async Task<List<string>> RunOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context) { ILogger logger = context.CreateReplaySafeLogger(SayHelloOrchestrator); logger.LogInformation("Saying hello."); var outputs = new List<string>(); // Replace name and input with values relevant for your Durable Functions Activity outputs.Add(await context.CallActivityAsync<string>(SayHelloOneActivitity, "Tokyo")); outputs.Add(await context.CallActivityAsync<string>(SayHelloOneActivitity, "Seattle")); outputs.Add(await context.CallActivityAsync<string>(SayHelloOneActivitity, "London")); // returns ["Hello Tokyo!", "Hello Seattle!", "Hello London!"] return outputs; } const string EventName = "EventToWaitFor"; [Function(SayHelloEventOrchestrator)] public static async Task<List<string>> RunSayHelloEventOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context, [DurableClient] DurableTaskClient durableTaskClient) { ILogger logger = context.CreateReplaySafeLogger(SayHelloOrchestrator); logger.LogInformation("Saying hello."); var outputs = new List<string>(); // Replace name and input with values relevant for your Durable Functions Activity var task = context.CallSubOrchestratorAsync(SayHelloEventSubOrchestrator); await context.CreateTimer(TimeSpan.FromSeconds(15), CancellationToken.None); await context.CallActivityAsync(nameof(RaiseEventActivity), new EventData(context.InstanceId, EventName, "Data")); await context.CallActivityAsync(nameof(RaiseEventActivity), new EventData(context.InstanceId, EventName, "Data")); var eventValue = await context.WaitForExternalEvent<string>(EventName); logger.LogInformation($"Event received by main orchestration: {eventValue}"); await task; logger.LogInformation($"Sub orchestration finished and was awaited succesfully"); return outputs; } [Function(SayHelloEventSubOrchestrator)] public static async Task<List<string>> RunSayHelloEventSubOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context) { ILogger logger = context.CreateReplaySafeLogger(SayHelloOrchestrator); logger.LogInformation($"RunSayHelloEventSubOrchestrator started. InstanceId={context.InstanceId}"); var outputs = new List<string>(); var eventValue = await context.WaitForExternalEvent<string>(EventName); logger.LogInformation($"Event received by sub orchestration: {eventValue}"); return outputs; } [Function(nameof(SayHelloOneActivitity))] public static string SayHelloOne([ActivityTrigger] string name, FunctionContext executionContext) { ILogger logger = executionContext.GetLogger("SayHello"); logger.LogInformation("Saying hello to {name}.", name); return $"Hello {name}!"; } [Function(nameof(RaiseEventActivity))] public static async Task RaiseEventActivity([ActivityTrigger] EventData eventData, [DurableClient] DurableTaskClient client, FunctionContext executionContext) { ILogger logger = executionContext.GetLogger("RaiseEventActivity"); logger.LogInformation($"raising event {eventData}"); await client.RaiseEventAsync(instanceId: eventData.instanceId, eventName: eventData.eventName, eventPayload: eventData.data); } [Function("Jobs_HttpStart_Event")] public static async Task<HttpResponseData> Jobs_HttpStart_Event( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req, [DurableClient] DurableTaskClient client, FunctionContext executionContext) { ILogger logger = executionContext.GetLogger("Jobs_HttpStart_Event"); // Function input comes from the request content. string instanceId = await client.ScheduleNewOrchestrationInstanceAsync( SayHelloEventOrchestrator); logger.LogInformation("Started orchestration with ID = '{instanceId}'.", instanceId); // Returns an HTTP 202 response with an instance management payload. return await client.CreateCheckStatusResponseAsync(req, instanceId); } } }
运行日志
日志显示主编排成功接收事件,但子编排持续挂起:
For detailed output, run func with --verbose flag. [2024-07-02T04:06:42.063Z] Host lock lease acquired by instance ID '0000000000000000000000000C75A72E'. [2024-07-02T04:06:51.223Z] Executing 'Functions.Jobs_HttpStart_Event' (Reason='This function was programmatically called via the host APIs.', Id=86ba1f8b-9454-4158-9e7a-d1316a7a1140) [2024-07-02T04:06:51.858Z] Scheduling new Jobs_SayHelloEventOrchestrator orchestration with instance ID 'd0e8390352d1403b88e6fb763a33cbb2' and 0 bytes of input data. [2024-07-02T04:06:52.116Z] Started orchestration with ID = 'd0e8390352d1403b88e6fb763a33cbb2'. [2024-07-02T04:06:52.239Z] Executed 'Functions.Jobs_HttpStart_Event' (Succeeded, Id=86ba1f8b-9454-4158-9e7a-d1316a7a1140, Duration=1056ms) [2024-07-02T04:06:52.272Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=91ac3cd9-504b-4a6e-81f4-b55a4febdcc2) [2024-07-02T04:06:52.507Z] Saying hello. [2024-07-02T04:06:52.587Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=91ac3cd9-504b-4a6e-81f4-b55a4febdcc2, Duration=341ms) [2024-07-02T04:06:52.729Z] Executing 'Functions.Jobs_SayHelloEventSubOrchestrator' (Reason='(null)', Id=52250685-de55-4753-9eb5-a119bb2f14c1) [2024-07-02T04:06:52.768Z] RunSayHelloEventSubOrchestrator started. InstanceId=2236b5d8ffd15b7b9180de5d977309da [2024-07-02T04:06:52.781Z] Executed 'Functions.Jobs_SayHelloEventSubOrchestrator' (Succeeded, Id=52250685-de55-4753-9eb5-a119bb2f14c1, Duration=54ms) [2024-07-02T04:07:08.919Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=cf21b5f6-0582-4f7e-bfe8-bd7d2fe10fef) [2024-07-02T04:07:08.979Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=cf21b5f6-0582-4f7e-bfe8-bd7d2fe10fef, Duration=61ms) [2024-07-02T04:07:09.023Z] Executing 'Functions.RaiseEventActivity' (Reason='(null)', Id=afdcb70b-6f35-4b1e-a87d-72929ac86192) [2024-07-02T04:07:09.041Z] raising event EventData { instanceId = d0e8390352d1403b88e6fb763a33cbb2, eventName = EventToWaitFor, data = Data } [2024-07-02T04:07:09.100Z] Executed 'Functions.RaiseEventActivity' (Succeeded, Id=afdcb70b-6f35-4b1e-a87d-72929ac86192, Duration=81ms) [2024-07-02T04:07:09.115Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=71444a03-da20-4325-b60f-dba7bfe5b86e) [2024-07-02T04:07:09.149Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=71444a03-da20-4325-b60f-dba7bfe5b86e, Duration=36ms) [2024-07-02T04:07:09.203Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=81b80312-48f0-4aba-8d42-4a9fd384966b) [2024-07-02T04:07:09.238Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=81b80312-48f0-4aba-8d42-4a9fd384966b, Duration=35ms) [2024-07-02T04:07:09.264Z] Executing 'Functions.RaiseEventActivity' (Reason='(null)', Id=e8417808-7b31-41fc-bdbc-87358e714c9b) [2024-07-02T04:07:09.286Z] raising event EventData { instanceId = d0e8390352d1403b88e6fb763a33cbb2, eventName = EventToWaitFor, data = Data } [2024-07-02T04:07:09.334Z] Executed 'Functions.RaiseEventActivity' (Succeeded, Id=e8417808-7b31-41fc-bdbc-87358e714c9b, Duration=70ms) [2024-07-02T04:07:09.352Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=44627a58-4494-4099-a5b5-435bf1a4b994) [2024-07-02T04:07:09.373Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=44627a58-4494-4099-a5b5-435bf1a4b994, Duration=20ms) [2024-07-02T04:07:12.545Z] Executing 'Functions.Jobs_SayHelloEventOrchestrator' (Reason='(null)', Id=9b3d7f92-a6fe-4ffb-b072-8fbca7a03c6f) [2024-07-02T04:07:12.561Z] Event received by main orchestration: Data [2024-07-02T04:07:12.567Z] Executed 'Functions.Jobs_SayHelloEventOrchestrator' (Succeeded, Id=9b3d7f92-a6fe-4ffb-b072-8fbca7a03c6f, Duration=22ms)
解决方案
方案1:调用子编排时指定自定义实例ID
这是最简洁的方案,不需要子编排做任何修改。主编排可以提前生成子编排的实例ID,直接传入调用方法:
[Function(SayHelloEventOrchestrator)] public static async Task<List<string>> RunSayHelloEventOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context, [DurableClient] DurableTaskClient durableTaskClient) { ILogger logger = context.CreateReplaySafeLogger(SayHelloOrchestrator); logger.LogInformation("Saying hello."); var outputs = new List<string>(); // 自定义子编排ID,比如基于主编排ID+唯一后缀 string subInstanceId = $"{context.InstanceId}-sub-orchestrator"; var subTask = context.CallSubOrchestratorAsync(SayHelloEventSubOrchestrator, instanceId: subInstanceId); await context.CreateTimer(TimeSpan.FromSeconds(15), CancellationToken.None); // 直接用自定义的子编排ID发送事件 await context.CallActivityAsync(nameof(RaiseEventActivity), new EventData(subInstanceId, EventName, "Data")); var eventValue = await context.WaitForExternalEvent<string>(EventName); logger.LogInformation($"Event received by main orchestration: {eventValue}"); await subTask; logger.LogInformation($"Sub orchestration finished and was awaited succesfully"); return outputs; }
方案2:子编排返回自身实例ID
如果不想自定义ID,可以让子编排启动后返回自己的InstanceId,主编排拿到后再发送事件:
修改子编排的返回类型:
[Function(SayHelloEventSubOrchestrator)] public static async Task<string> RunSayHelloEventSubOrchestrator( [OrchestrationTrigger] TaskOrchestrationContext context) { ILogger logger = context.CreateReplaySafeLogger(SayHelloOrchestrator); logger.LogInformation($"RunSayHelloEventSubOrchestrator started. InstanceId={context.InstanceId}"); // 先启动事件等待任务,再返回实例ID var eventTask = context.WaitForExternalEvent<string>(EventName); // 确保编排能正确完成checkpoint,避免 replay 问题 await Task.Yield(); // 返回实例ID给主编排 string instanceId = context.InstanceId
相关产品推荐
相关产品推荐

