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. 异常产生的原因
该异常并非业务代码抛出,而是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
相关产品推荐
相关产品推荐

