C# 什么';在任务体中使用.Result和在异步方法中使用await有什么区别?

C# 什么';在任务体中使用.Result和在异步方法中使用await有什么区别?,c#,multithreading,asynchronous,async-await,task,C#,Multithreading,Asynchronous,Async Await,Task,我设法在网上找到了一些类似问题的答案,但没有一个解释得足够令人满意,足以让我理解下面代码中的区别 我知道wait和.Result不同,它不会阻止调用线程。但是,如果我们试图从一个任务访问这个属性,而这个任务无论如何都不会阻止它呢 例如,这两者之间有什么区别吗 public static Task PrintPageAsync(string url) { return Task.Run(() => { WebRequest webRequest = WebRe

我设法在网上找到了一些类似问题的答案,但没有一个解释得足够令人满意,足以让我理解下面代码中的区别

我知道wait和.Result不同,它不会阻止调用线程。但是,如果我们试图从一个任务访问这个属性,而这个任务无论如何都不会阻止它呢

例如,这两者之间有什么区别吗

public static Task PrintPageAsync(string url)
{
    return Task.Run(() =>
    {
        WebRequest webRequest = WebRequest.Create(url);
        WebResponse response = webRequest.GetResponseAsync().Result;
        using (StreamReader reader = new StreamReader(response.GetResponseStream()))
        {
            string text = reader.ReadToEndAsync().Result;
            Console.WriteLine(text);
        }
    });
}
还有这个

public static async Task PrintPageAsync(string url)
{
    WebRequest webRequest = WebRequest.Create(url);
    WebResponse response = await webRequest.GetResponseAsync();
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        string text = await reader.ReadToEndAsync();
        Console.WriteLine(text);
    }
}

.Result
将同步执行您的代码,即您忽略了
任务的本质以及整个
TPL
的支持<代码>等待
,正如它所说的,是编译器以一种很好的旧的“回调”方式(a-ka典型的
JavaScript
)重写方法的标记,这是一种异步方式来完成完全相同的计算


更简单:只要可能,您应该更喜欢
wait
而不是
。Result

由于第一个示例在线程池线程上启动一个新任务,因此当存在等效的同步方法时,调用异步方法是不必要的。它没有任何好处,只是在管理异步任务时增加了一些不必要的开销。只需使用
.GetResponse()
而不是
.GetResponseAsync().Result
.ReadToEnd()
而不是
.ReadToEndAsync().Result

与.Result不同,wait不会阻止调用线程

这并不总是正确的。等待的方法可以(部分或完全)同步执行,如果它们决定这样做的话

例如,有什么区别吗

这两个例子有很多不同之处。虽然它们在某些情况下可能无关紧要,但在另一种情况下可能至关重要:

  • 聚合异常

    当任务中出现异常或任务被取消时,调用
    task.Result
    将异常包装在
    aggregateeexception
    中。相反,
    等待
    等待此任务将抛出原始异常。因此,在捕获特定异常时必须小心

  • 线程

    在第一个示例中,整个方法将在同一个(线程池)线程上执行。第二个示例可以在几个不同的线程上执行,具体取决于当前的
    SynchronizationContext
    。应避免使用对线程关联敏感的代码

  • 同步上下文

    第一个示例将在没有同步上下文的情况下执行,而第二个示例将在每次等待后恢复原始的同步上下文。对于控制台应用程序,这并不重要。但在WPF或WinForms应用程序中,只能从相应的同步上下文访问UI元素

  • 异步执行

    在第一个示例中,
    PrintPageAsync
    将在新任务排队等待执行后立即返回,而第二个将同步执行,直到第一个
    wait
    (或者甚至可能在此之后)。这可能会对GUI responsivnes产生严重影响,尤其是当异步方法使用
    WebRequest
    时,因为
    GetResponseAsync()
    方法同步执行DNS解析(请参阅)。因此,当从UI线程调用使用
    WebRequest
    的方法时,建议在
    Task.Run()中包装代码


  • 没关系,区别仍然是它会阻塞。同样,将异步和阻塞混合在一起也会导致代码锁定。这很有趣,你所说的“它将阻塞”是什么意思?我在Main方法中使用它,如下所示:PrintPageAsync(@);while(true){Console.Write(“*”)}无论我使用哪一个,它都不会阻塞。它会立即开始打印星号,一段时间后它会打印页面,并且在这之后仍会打印星号。这是因为第一个是在另一个线程中启动的。在这种情况下,您不妨调用同步的
    ReadToEnd
    。删除
    任务。运行
    ,看看会发生什么pens。我想我理解,但我的重点是,当我使用Task.Run时,两者之间有什么区别,所以它从另一个线程开始。调用它时,它的行为会与异步方法不同吗?最终的区别是使用了多少线程。对于这个简单的示例,差别不大,但如果这是在se上编写的代码web服务调用背后的服务器,那么当您收到许多请求时,服务器会因为一堆阻塞的线程而变慢。这有什么关系呢?如果我们在任务体中使用它,它不是只在任务体中阻塞,并且调用方法不受影响吗?无论我使用哪个方法,它们都会给我相同的结果。例如,PrintPageAsync(@”);虽然(true){Console.Write(“*”;}在Main方法中使用此选项会打印星号,在一段时间后打印页面内容并再次打印星号。我认为您必须深入了解异步性是什么。它会一直阻塞,直到您得到底层计算的结果,或者异常情况下被取消/失败。任何一种方式都意味着同步执行,这与任务的目的相反。我理解其中的区别,但我认为,因为我们在另一个线程中启动任务,所以它不会阻止调用线程,当我们使用阻止时。结果,我们阻止了任务的线程,而不是调用线程。我完全错了吗?另外,正如我所提到的,这两种方法的输出(使用我前面评论中的代码时)是相同的。如果其中一个阻塞,而另一个不阻塞,那么为什么输出是相同的?@pink.Overload您的示例只是误用了任务。除非有绝对必要,否则不要将
    任务
    包装到
    任务
    中。这样的代码在线程队列消耗方面效率非常低