You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

隔离模式下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
相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 14:24:56