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

Azure HttpTrigger函数调用方提前关闭引发异常,如何无需等待响应规避?

问题描述

我在Visual Studio中创建了一个包含HttpTrigger函数的Azure Function项目,函数代码如下:

public class DoSomething
{
   private readonly ILogger<DoSomething> _logger;

   public DoSomething(ILogger<DoSomething> logger)
   {
       _logger = logger;
   }

   [Function("DoSomething")]
   public async Task Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
   {
       try
       { 
           _logger.LogInformation("C# HTTP trigger function processed a request.");
           for (int i = 0; i<7; i++)
           {
               _logger.LogInformation($"Working... {i}");
               await Task.Delay(1000);
           }
           _logger.LogInformation("Done");
       }
       catch (Exception ex)
       {
           _logger.LogError(ex, "An error occurred.");
       }
       _logger.LogInformation("After Exception");
       return;
   }
}

使用以下控制台应用调用该函数:

Task.Run(async () =>
{
    using var client = new HttpClient();
    var res = await client.PostAsync("http://localhost:7129/api/DoSomething", null);
});

//执行其他操作
await Task.Delay(4000);

故意让控制台应用在函数完成工作前关闭,此时函数日志出现异常信息:

[2025-02-06T10:38:32.483Z] C# HTTP trigger function processed a request.
[2025-02-06T10:38:32.485Z] Working... 0
[2025-02-06T10:38:33.486Z] Working... 1
[2025-02-06T10:38:34.491Z] Working... 2
[2025-02-06T10:38:35.497Z] Working... 3
[2025-02-06T10:38:36.508Z] Working... 4
[2025-02-06T10:38:37.518Z] Working... 5
[2025-02-06T10:38:38.533Z] Working... 6
[2025-02-06T10:38:39.549Z] Done
[2025-02-06T10:38:39.552Z] After Exception
[2025-02-06T10:38:39.593Z] Function 'DoSomething', Invocation id '5a240a47-710b-482e-8d43-3a0fdf08c73b': An exception was thrown by the invocation.
[2025-02-06T10:38:39.596Z] Result: Function 'DoSomething', Invocation id '5a240a47-710b-482e-8d43-3a0fdf08c73b': An exception was thrown by the invocation.
Exception: System.InvalidOperationException: An attempt was made to transition a task to a final state when it had already completed.
[2025-02-06T10:38:39.599Z]    at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled(CancellationToken cancellationToken)
[2025-02-06T10:38:39.601Z]    at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
[2025-02-06T10:38:39.603Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.ContextReference.CompleteFunction() in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\Coordinator\ContextReference.cs:line 59
[2025-02-06T10:38:39.606Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.DefaultHttpCoordinator.CompleteFunctionInvocation(String invocationId) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\Coordinator\DefaultHttpCoordinator.cs:line 55
[2025-02-06T10:38:39.608Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 69
[2025-02-06T10:38:39.612Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77
Stack:    at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled(CancellationToken cancellationToken)
[2025-02-06T10:38:39.614Z]    at System.Threading.Tasks.TaskCompletionSource`1.SetCanceled()
[2025-02-06T10:38:39.616Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.ContextReference.CompleteFunction() in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\Coordinator\ContextReference.cs:line 59
[2025-02-06T10:38:39.618Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.DefaultHttpCoordinator.CompleteFunctionInvocation(String invocationId) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\Coordinator\DefaultHttpCoordinator.cs:line 55
[2025-02-06T10:38:39.621Z]    at Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore.FunctionsHttpProxyingMiddleware.Invoke(FunctionContext context, FunctionExecutionDelegate next) in D:\a\_work\1\s\extensions\Worker.Extensions.Http.AspNetCore\src\FunctionsMiddleware\FunctionsHttpProxyingMiddleware.cs:line 69
[2025-02-06T10:38:39.624Z]    at Microsoft.Azure.Functions.Worker.FunctionsApplication.InvokeFunctionAsync(FunctionContext context) in D:\a\_work\1\s\src\DotNetWorker.Core\FunctionsApplication.cs:line 77.
[2025-02-06T10:38:39.678Z] Executed 'Functions.DoSomething' (Failed, Id=5a240a47-710b-482e-8d43-3a0fdf08c73b, Duration=7506ms)
[2025-02-06T10:38:39.680Z] System.Private.CoreLib: Exception while executing function: Functions.DoSomething. Microsoft.Azure.WebJobs.Script.Grpc: Failed to proxy request with ForwarderError: RequestCanceled. System.Net.Http: The operation was canceled. System.Net.Sockets: Unable to read data from the transport connection: Operazione di I/O terminata a causa dell'uscita dal thread oppure della richiesta di un'applicazione.. Operazione di I/O terminata a causa dell'uscita dal thread oppure della richiesta di un'applicazione.

疑问:

  1. 为何函数代码会抛出该异常?原本以为异常会在函数执行过程中出现;
  2. 有没有办法在无需等待函数响应的情况下避免该异常?

解答

1. 异常产生的原因

该异常并非业务代码抛出,而是Azure Functions的HTTP扩展组件内部逻辑冲突导致:

  • 客户端提前关闭连接时,HTTP传输通道中断,Functions的HTTP中间件会检测到取消信号,尝试将函数调用对应的TaskCompletionSource标记为已取消;
  • 但此时业务代码已经正常执行完毕(日志显示Done和After Exception),对应的任务已处于完成状态;
  • 扩展组件试图对已完成的任务设置取消状态,触发System.InvalidOperationException异常。

简言之,客户端断开连接的取消信号与函数正常完成的信号,在扩展内部产生时序冲突,导致报错。

2. 无需等待响应避免异常的方案

方案一:改用Queue Trigger(推荐)

HTTP Trigger是请求-响应模式,若不需要等待结果,更适合使用异步触发机制:

  • 客户端调用时,向Azure Storage Queue发送任务消息;
  • 将原函数修改为Queue Trigger,监听并处理队列消息。
    这种模式下,客户端发送消息后即可断开,函数在后台异步执行,完全不存在HTTP连接中断的问题,从根源上避免异常。

方案二:HTTP函数立即返回响应,后台执行任务

若必须使用HTTP Trigger,可以让函数先返回响应,再在后台执行任务:

[Function("DoSomething")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequest req)
{
    // 确保响应先发送给客户端
    await Task.Yield();
    
    // 后台执行任务,无需等待
    _ = Task.Run(async () =>
    {
        try
        { 
            _logger.LogInformation("C# HTTP trigger function processed a request.");
            for (int i = 0; i<7; i++)
            {
                _logger.LogInformation($"Working... {i}");
                await Task.Delay(1000);
            }
            _logger.LogInformation("Done");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An error occurred.");
        }
        _logger.LogInformation("After Exception");
    });
    
    return new OkResult();
}

注意:此方式下,后台任务不受Functions生命周期管理,若函数实例被回收,任务可能被终止。如需保证任务完成,优先选择Queue Trigger。

方案三:捕获扩展内部异常(不推荐)

可在函数应用中间件中捕获该特定异常,但这属于治标不治本的方式,可能影响其他正常逻辑,不建议采用。


内容的提问来源于stack exchange,提问作者Paolo Crociati

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:05:58