【问题标题】:Does Task.Result have any impact when used after the await statement?在 await 语句之后使用 Task.Result 有影响吗?
【发布时间】:2017-07-20 11:29:00
【问题描述】:

我正在尝试开发概念验证应用程序,我想了解以下两个异步方法的内部结构。

Task.Result 会导致DoSomethingAsync1() 出现任何问题吗?

我已经阅读了一些关于Task.Result 可能导致的阻塞和死锁问题,但我认为在这种情况下不会,因为它在单独的任务上运行并且任务上有一个await这已经确保了结果。

这里的主要目标是在一个单独的任务上运行异步方法,因为这个操作不依赖于主 asp.net 线程,更重要的是捕获DoSomethingThatTakesReallyLong()抛出的任何异常

另外,在ActionResultDoSomething(),我应该设置.ConfigureAwait(false);吗?

在以下场景中我需要注意任何隐藏的瓶颈/问题吗?

更新

我已经修正了在这里输入问题时的错字。 (返回用户对象而不是任务)

另外,我不能在实际应用程序中一次将所有高级方法转换为async。所以,这是我计划一步一步做的事情,从不依赖于主线程的操作开始。

我也非常感谢所有最佳实践的答案,但这是一个小操场代码,我的主要问题是知道 DoSomethingAsync1() 和 DoSomethingAsync2() 之间是否存在内部差异,这可能会导致某些问题健康)状况。

通过阅读 cmets 和答案,我知道没有太大区别。


  private static async Task<User> DoSomethingAsync1()
    {
        try
        {
            var longRunningTask = Task<User>.Factory.StartNew(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
            var user = await longRunningTask;
            //Will the below line of code cause any issue?
            Console.WriteLine(longRunningTask.Result.Id);
            return user;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return null;
        }
    }

    private static async Task<User> DoSomethingAsync2()
    {
        try
        {
            var longRunningTask = Task<User>.Factory.StartNew(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
            var user = await longRunningTask;
            Console.WriteLine(user.Id);
            return user;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex);
            return null;
        }
    }

   public ActionResult Index()
   {

    //Things that run on main Asp.NET thread

    //Things that run independently 
    //Won't be using the task returned here at the moment. 
    DoSomethingAsync1();

    //Do more things on main Asp.NET thread if needed

    return View();             

   }

https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html

https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

谢谢

【问题讨论】:

  • 我不希望您的第一个代码能够编译,因为您正试图从返回类型为 Task&lt;User&gt; 的 async 方法返回 Task&lt;User&gt;。该方法中的 return 语句应尝试返回 User。
  • 另外,Task&lt;T&gt;.Factory.StartNew() 是一个遗留函数,不应该与 async 关键字一起使用
  • 我在这里输入的时候一定是打错了。会更新的
  • 你为什么要删除@MikeT 的答案,我赞成!感谢您提供的一些澄清!
  • @Ren 因为我简化了一些细节而不是让你埋头于后端如何工作的细节中,所以它被拖了后腿,这不值得恶化

标签: c# .net multithreading async-await


【解决方案1】:

Task.Result 会导致任何问题[如果任务已经完成]?

Task&lt;T&gt;.Result会同步等待任务完成(同task.Wait()),然后返回结果(同await task)。

由于您的任务已经完成,Result 和 await 之间只有一个重要的区别:Result 将异常包装在 AggregateException 中。出于这个原因(也为了使代码更安全),我尽可能使用await 而不是Result。也就是说,采用DoSomethingAsync2 方法。

我已经阅读了一些关于 Task.Result 可能导致的阻塞和死锁问题,但我认为在这种情况下不会,因为它在单独的任务上运行并且已经等待任务确保结果。

它不会因为它在线程池上下文中运行。

所以,这是我计划一步一步做的事情,从不依赖于主线程的操作开始。

您可能会发现我的 brownfield async 文章很有帮助。在那篇文章中,我将这种技术称为“线程池黑客”。

在以下场景中我需要注意任何隐藏的瓶颈/问题吗?

一些:

You shouldn't use StartNew. Use Task.Run instead.

You shouldn't fire-and-forget on ASP.NET(即调用异步方法而不消耗其任务)。

You shouldn't expose fake-asynchronous methods(即具有异步签名但通过阻塞线程池线程实现的方法)。 Instead of using Task.Run to implement a method, use Task.Run to call a method.

结合这三点,The Thread Pool Hack 的正确结构是使用Task.Run 和GetAwaiter().GetResult()(这就像Result,但避免了AggregateException 包装器):

public ActionResult Index()
{
  var r = Task.Run(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong())
      .GetAwaiter().GetResult();
  return View();             
}

作为最后一个问题,只要您的 ASP.NET 应用程序中有这种模式,您的应用程序的可伸缩性就会降低。 Instead of using one thread during DoSomethingThatTakesReallyLong, your app is now using two; this may also throw off the ASP.NET thread pool heuristics。在过渡到 async 期间这可能是可以接受的,但请记住它是完成过渡的动力。

【讨论】:

  • 非常感谢您一如既往地提供深刻的见解和清晰的解释!当然,我会查看您关于 Brownfield 异步开发的帖子。
【解决方案2】:

正常的用法是这样的:

private static async Task<User> DoSomethingAsync3()
{
    try
    {
        var user = await Task.Run(() => LongRunner.LongRunnerInstance.DoSomethingThatTakesReallyLong());
        Console.WriteLine(user.Id);
        return user;
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
}

另外,如果您要正确遵循异步模式,您应该将您的索引更改为:

public async Task<ActionResult> Index()
{
     await DoSomethingAsync3();
     return View();
}

【讨论】:

  • 回复:你的第二个子弹 - 我肯定在那里看到了 await。你不是吗?
  • @Damien_The_Unbeliever 哎呀,我根本没看到。将编辑
  • 我在这里输入的时候一定是打错了。将更新。我提到我上面的代码是一个概念证明。实际的应用程序要大得多,我不能随意使所有更高级别的代码一次全部异步。我对最佳实践有所了解,包括应该首选 Task.Run() 的事实。这是遗留代码,所以我想让 PoC 代码相似。感谢您提供有关最佳实践的解决方案。如果您还可以就我关于等待后 task.result 使用的问题提供更多见解,那就太好了。
  • @Ren 在 await 之后没有 Task.Result 因为 await 给你如果你正确使用它的结果,如我的代码所示
  • @Ren 答案已编辑。最重要的一点是尽可能地遵循异步模式。您不必一次转换所有代码,您可以通过使入口点(控制器的操作)异步来逐步完成
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-08-14
相关资源
最近更新 更多