AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / coding / 问题 / 79558419
Accepted
Pragmateek
Pragmateek
Asked: 2025-04-06 23:53:45 +0800 CST2025-04-06 23:53:45 +0800 CST 2025-04-06 23:53:45 +0800 CST

为什么最小 API 端点正在等待任务?

  • 772

构建基本的 ASP.NET Core Minimal API 时,我惊讶地发现,如果端点返回Task,则请求直到Task完成才会返回:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton<ICounterService, CounterService>();

var app = builder.Build();

app.MapGet("/counter/value", (ICounterService counter) => counter.Value);
app.MapGet("/counter/incrementnow", (ICounterService counter) => counter.Increment());
app.MapGet("/counter/incrementlater", (ICounterService counter) => Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith(_ => counter.Increment()));
app.MapGet("/counter/incrementlaternowait", (ICounterService counter) => { Task.Delay(TimeSpan.FromSeconds(5)).ContinueWith(_ => counter.Increment()); return; });

app.Run();

interface ICounterService
{
    int Value { get; }
    void Increment();
}

class CounterService : ICounterService
{
    public int Value { get; private set; }

    public void Increment()
    {
        int previousValue = Value++;
        Console.WriteLine($"Incremented from {previousValue} to {Value}.");
    }
}

这种行为记录在哪里?

await如果我在端点中明确执行该任务,我就会期待这种行为async。

c#
  • 3 3 个回答
  • 128 Views

3 个回答

  • Voted
  1. Best Answer
    Ivan Petrov
    2025-04-07T00:31:53+08:002025-04-07T00:31:53+08:00

    我认为它没有明确的文档记录。文档让它看起来好像只允许返回Task(没有提到awaiting 或它是特殊类型):

    最小端点支持以下类型的返回值:

    1. string- 其中包括Task<string>和ValueTask<string>。
    2. T(任何其他类型)- 包括Task<T>和ValueTask<T>。
    3. IResult基于 - 包括Task<IResult>和ValueTask<IResult>。

    因此, Task“任何其他类型T”都不是string,也没有实现IResult,因此它应该作为序列化返回json,而不是明确地await被编辑。

    然而,端点(控制器操作方法或最小 API lambda)被转换成RequestDelegate:

    public delegate Task RequestDelegate(HttpContext context);
    

    在RequestDelegateFactory将它们插入中间件管道之前,您基本上已经拥有:

    await requestDelegate();
    

    其神奇之处RequestDelegateFactory在于确保上述代码也await能返回任何可等待类型,例如你的方法/lambda 返回的类型。另请参阅此问题。 awaitTask

    因此Task,作为返回类型具有特殊含义——同步返回方法/lambda 的异步版本void。回到上面的文档

    1. T(任何其他类型)- 包括Task<T>和ValueTask<T>。

    在我们的例子中,这个上下文实际上是一个,而T不是它自己的类型。typeof(void)TaskTask<void>T

    我认为这种解释得到了 asp.net 核心团队的证实,他们承认 .NET7+ 过滤器子管道逻辑中存在一个错误(路由处理程序过滤器不处理所有返回类型),因为Task对象被包装为普通对象,ValueTask<object>而不是在普通中间件中等待 -ValueTask<object>而是等待包装 - 因此可以将其作为json响应返回,而不是awaiting它:


    Task Method() => Task.CompletedTask;
    app.MapGet("/method", Method).AddFilter((c, n) => n(c));
    
    

    输出:

    {"result":{},"id":54,"exception":null,"status":5,"isCanceled":false,"isCompleted":true,"isCompletedSuccessfully":true,"creationOptions":0,"asyncState":null,"isFaulted":false}
    

    错误描述:

    过滤器当前会将用户委托包装在 中,ValueTask<object?>然后再将函数移交给正常的路由处理逻辑。这会导致任何非对象返回方法(例如void、Task、Task<T>等)出现问题。

    因此他们必须改变RequestDelegateFactory逻辑来实际执行构造的委托,而不是依赖于管道中的await顶层-源代码:await

    private static ValueTask<object?> ExecuteTaskWithEmptyResult(Task task) {
        static async ValueTask<object?> ExecuteAwaited(Task task) {
            await task;
            return EmptyHttpResult.Instance;
        }
    
        if (task.IsCompletedSuccessfully) {
            return new ValueTask<object?>(EmptyHttpResult.Instance);
        }
    
        return ExecuteAwaited(task);
    }
    

    如果我们想返回一个Task不await适合我们的,我们可以使用返回的 lambdaTask<Task>和一个过滤器。

    app.MapGet("/taskreturn", () => Task.FromResult(Task.Delay(5000)))
    .AddEndpointFilter(async (context, next) =>
    {
        var result = await next(context); // next gives us Task<Task>
        // if we return result here it will be the uncompleted task
        // immediately
        //return result;
    
        if (result is Task task)
        {
            await task; // wait for 5 seconds
            //return task;
            // if we return here it will be the completed task
        }
    
        // or we can return something else
        return "from filter";
    });
    
    
    • 3
  2. MrC aka Shaun Curtis
    2025-04-07T02:19:57+08:002025-04-07T02:19:57+08:00

    有据可查:你只需稍微挖掘一下。怪搜索引擎推广垃圾内容,压倒真正的内容!

    MapGet是一种扩展方法,其中定义了两种模式public static class EndpointRouteBuilderExtensions:

    public static IEndpointConventionBuilder MapGet(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
    
    public static IEndpointConventionBuilder MapGet(this IEndpointRouteBuilder endpoints, string pattern, delegate handler);
    
    public delegate Task RequestDelegate(HttpContext context);
    

    以及 MSDocs 链接:

    https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.endpointroutebuilderextensions?view=aspnetcore-9.0

    令我惊讶的是,如果端点返回任务,则请求直到任务完成才会返回

    您不能返回Task。如果您希望在第一个 yield 上返回,请返回null,而不是返回 Task 到端点。

    因此,您可以像这样重写代码,等待任务完成:

    app.MapGet("/counter/incrementlater", IncrementLater);
    
    //...
    
    app.Run();
    
    async Task IncrementLater(HttpRequest request, ICounterService counter )
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
        counter.Increment();
    }
    

    或者这将在第一个收益即 Task.Delay 时返回):

    app.MapGet("/counter/incrementlater", IncrementLater);
    
    //...
    
    app.Run();
    
    async void IncrementLater(HttpRequest request, ICounterService counter )
    {
        await Task.Delay(TimeSpan.FromSeconds(5));
        counter.Increment();
    }
    
    • 2
  3. poke
    2025-04-07T20:04:24+08:002025-04-07T20:04:24+08:00

    最小 API 端点委托的行为与经典 MVC 中的控制器操作相同。因此,如果您返回一个,Task则意味着您的端点/操作是异步的,并且框架仍需要等待正在进行的工作来确定实际的 HTTP 响应是什么样子。

    这里的 Minimal API 没什么特别之处。你似乎感到困惑,因为端点委托没有使用async和 ,await但这两者都不是控制器操作中必需的。例如,以下是一个完全正常的异步控制器操作:

    public Task<string[]> GetWeatherData()
    {
        return _weatherService.GetWeatherDataAsync();
    }
    

    从语义上讲,这也与以下使用相同async/await:

    public async Task<string[]> GetWeatherData()
    {
        return await _weatherService.GetWeatherDataAsync();
    }
    

    但是由于_weatherService.GetWeatherDataAsync已经返回了一项任务,您不一定非要async对其采取行动await,而可以直接从天气服务返回该任务(尽管这会产生一些可能需要或不需要的效果)。

    在这两种情况下,操作都会返回一个Task<string[]>,这将使 ASP.NET Core await 该操作以等待响应。这async只会告诉编译器执行它的操作,以便您能够使用await。但它对运行时来说几乎是不可见的(并且无关紧要)。


    您在评论中提到了“发射后不管”。如果您确实希望某个端点(最小 API 或控制器操作)在后台运行某些操作的同时也能像“发射后不管”一样运行,则需要通过创建新线程将此操作明确移至后台。这可以很简单,例如Task.Run:

    // I’m using actions here but this would be identical in Minimal API delegates
    public async Task<IActionResult> GetWeatherData()
    {
        // do not await the completion of this task
        _ = Task.Run(async () =>
        {
            await Task.Delay(5000);
            var data = await _weatherService.GetWeatherDataAsync();
            _logger.LogInformation("Received weather data in the background: {Data}", data);
        });
    
        await Task.Delay(1000);
        return Ok();
    }
    

    或者使用你的例子:

    app.MapGet("/counter/incrementlater", (ICounterService counter) =>
    {
        // do not await the completion of this task
        _ = Task.Run(async () =>
        {
            await Task.Delay(TimeSpan.FromSeconds(5));
            counter.Increment();
        }
    });
    

    请注意,在这种情况下,使用counter将会起作用,因为它已注册为单例服务。对于其他服务范围,尤其是范围服务(例如 EF 数据库上下文),这将不起作用,您需要在后台任务中创建自己的服务范围,以防止在端点/操作完成时(早于后台作业完成)处置范围服务。

    • 2

相关问题

  • Polly DecorlatedJitterBackoffV2 - 如何计算完成所有重试所需的最长时间?

  • Wpf。在 ScrollViewer 中滚动 DataGrid

  • 我在使用 .NET MAUI MVVM 的游戏页面上获得的分数在其他页面上不可见。如何在本地设备中保存分数数据

  • 从 DataTemplate 内部将 TreeView 层次结构与 HierarchicalDataTemplate 结合使用

  • 如何改进 .NET 中的验证接口?

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    重新格式化数字,在固定位置插入分隔符

    • 6 个回答
  • Marko Smith

    为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会?

    • 2 个回答
  • Marko Smith

    VScode 自动卸载扩展的问题(Material 主题)

    • 2 个回答
  • Marko Smith

    Vue 3:创建时出错“预期标识符但发现‘导入’”[重复]

    • 1 个回答
  • Marko Smith

    具有指定基础类型但没有枚举器的“枚举类”的用途是什么?

    • 1 个回答
  • Marko Smith

    如何修复未手动导入的模块的 MODULE_NOT_FOUND 错误?

    • 6 个回答
  • Marko Smith

    `(表达式,左值) = 右值` 在 C 或 C++ 中是有效的赋值吗?为什么有些编译器会接受/拒绝它?

    • 3 个回答
  • Marko Smith

    在 C++ 中,一个不执行任何操作的空程序需要 204KB 的堆,但在 C 中则不需要

    • 1 个回答
  • Marko Smith

    PowerBI 目前与 BigQuery 不兼容:Simba 驱动程序与 Windows 更新有关

    • 2 个回答
  • Marko Smith

    AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String”

    • 1 个回答
  • Martin Hope
    Fantastic Mr Fox msvc std::vector 实现中仅不接受可复制类型 2025-04-23 06:40:49 +0800 CST
  • Martin Hope
    Howard Hinnant 使用 chrono 查找下一个工作日 2025-04-21 08:30:25 +0800 CST
  • Martin Hope
    Fedor 构造函数的成员初始化程序可以包含另一个成员的初始化吗? 2025-04-15 01:01:44 +0800 CST
  • Martin Hope
    Petr Filipský 为什么 C++20 概念会导致循环约束错误,而老式的 SFINAE 不会? 2025-03-23 21:39:40 +0800 CST
  • Martin Hope
    Catskul C++20 是否进行了更改,允许从已知绑定数组“type(&)[N]”转换为未知绑定数组“type(&)[]”? 2025-03-04 06:57:53 +0800 CST
  • Martin Hope
    Stefan Pochmann 为什么 {2,3,10} 和 {x,3,10} (x=2) 的顺序不同? 2025-01-13 23:24:07 +0800 CST
  • Martin Hope
    Chad Feller 在 5.2 版中,bash 条件语句中的 [[ .. ]] 中的分号现在是可选的吗? 2024-10-21 05:50:33 +0800 CST
  • Martin Hope
    Wrench 为什么双破折号 (--) 会导致此 MariaDB 子句评估为 true? 2024-05-05 13:37:20 +0800 CST
  • Martin Hope
    Waket Zheng 为什么 `dict(id=1, **{'id': 2})` 有时会引发 `KeyError: 'id'` 而不是 TypeError? 2024-05-04 14:19:19 +0800 CST
  • Martin Hope
    user924 AdMob:MobileAds.initialize() - 对于某些设备,“java.lang.Integer 无法转换为 java.lang.String” 2024-03-20 03:12:31 +0800 CST

热门标签

python javascript c++ c# java typescript sql reactjs html

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve