【问题标题】:Debugging async await controller action调试异步等待控制器操作
【发布时间】:2016-03-16 04:48:43
【问题描述】:

我在 ApiController 中实现了(我认为是)一个简单的异步方法,如下所示:

public class CommentsController : ApiController
{
    private static readonly IList<CommentModel> Comments;

    static CommentsController()
    {
        // Note: Removed to abbreviate - populate comments manually here.
    }

    [ResponseType(typeof (IList<CommentModel>))]
    [HttpGet]
    public async Task<IHttpActionResult> GetAllComments()
    {
        return await Task.Factory.StartNew(() =>
        {
            Thread.Sleep(10000); // Breakpoint #1
            return Ok(Comments); // Breakpoint #2
        });

        // Breakpoint #3
    }
}

注意我在上面放置的断点。

当我点击继续时,我预计会发生在#1,线程等待,但在这个阶段流程继续通过#3。

然后当睡眠结束后,再次继续并在#2处休息。

但是在调试过程中它似乎是同步的。

我的问题首先是这真的是异步的,我如何调试它来验证,或者通过单元测试?

【问题讨论】:

  • 附言。我直接从浏览器访问 api - 也许这就是问题所在?

标签: c# asp.net-web-api visual-studio-2015 async-await


【解决方案1】:

但是在调试过程中它似乎是同步的。

它是串行的(不是同步的),因为await 关键字告诉方法在操作完成之前不要继续。调试器会以串行方式单步执行异步方法,这可以使它们看起来是同步的;这比让调试器跳转到不相关的代码然后再跳回来要自然得多。

我的问题首先是这真的是异步的

这是假异步的。在实际代码中,您永远不会使用 Task.Factory.StarNew

此外,对于 ASP.NET 应用程序,您应该避免将工作发送到线程池(StartNewTask.Run 等)。相反,您应该调用自然异步 API。

我如何调试它以验证,或以其他方式进行单元测试?

您可以调用该方法,验证该任务尚未完成,然后等待它。请注意,为避免竞争条件,您应该将控制器正在使用的任何异步服务存根。

【讨论】:

    【解决方案2】:

    断点 3 永远不会被击中,也不应该被击中。

    Task.Factory.StartNew 开头的Task 将异步运行。
    但是await 基本上是在继续当前方法之前等待Task 完成执行,并将执行返回给调用者。因此它为给定的Task 添加了一个延续
    由于您要返回 Task 的结果,因此 Breakpoint 3 将永远不会执行。

    如果你想让 Breakpoint 3Task 之后执行,你基本上有两个选择:

    要么你添加一个延续:

        return await Task.Factory.StartNew(() =>
        {
            Thread.Sleep(10000); // Breakpoint #1
            return Ok(Comments); // Breakpoint #2
        }).ContinueWith((Task<IHttpActionResult> t) => 
        {
            //t.Result contains the result of the previous Task
            return t.Result; // Breakpoint #3
        });
    

    或者(更简单),使用临时值和await

        var temp = await Task.Factory.StartNew(() =>
        {
            Thread.Sleep(10000); // Breakpoint #1
            return Ok(Comments); // Breakpoint #2
        });
    
        // Breakpoint #3
        return temp;
    

    要在不等待执行完成的情况下调用async 方法,只需省略await。所以,如果你想并行执行GetAllComments 5 次,你可以这样做:

    for(int i = 0; i < 5; i++)
        GetAllComments();
    

    【讨论】:

    • 您关于并行执行的最后陈述是危险的。如果没有未完成的未完成请求,即使后台任务仍在运行,在 Asp.Net 应用程序域中也可能会被拆除。您必须存储GetAllComments() 返回的任务并执行await Task.WhenAll( 以确保它们在请求返回之前完成,或者使用HostingEnvironment.QueueBackgroundWorkItem( 将每个任务排队,这不会保证任务将完成但它会延迟关机默认情况下让任务完成额外的 90 秒。
    【解决方案3】:

    断点#3 将如何被击中?在等待之前你已经得到了回报。

    因此,await 将产生一个状态机,并且在您的线程完成时将简单地返回该值。

    【讨论】:

      猜你喜欢
      • 2023-03-08
      • 2012-10-14
      • 2014-05-15
      • 2015-10-05
      • 1970-01-01
      • 2017-10-08
      • 2018-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多