【问题标题】:Is there any difference between Task.Run(async () => await MethodAsync()).Result and MethodAsync().Result? [closed]Task.Run(async () => await MethodAsync()).Result 和 MethodAsync().Result 之间有什么区别吗? [关闭]
【发布时间】:2019-10-07 21:25:44
【问题描述】:

我需要实现一个不支持异步的第三方接口,特别是来自 automapper 的 IValueResolver。

我想知道这两段代码有什么区别?使用第一个而不是第二个有什么好处吗?我将在 MethodAsync() 上调用外部异步 API

会同时锁定线程还是只锁定第二个?

1

var myValue = Task.Run(async () => await MethodAsync()).Result;

2

var myValue = MethodAsync().Result;

【问题讨论】:

  • 取决于Method的实现。 Task.Run 保证产生一个新线程。只是调用异步方法是不行的。
  • 我将调用外部 API
  • 锁定线程还是只锁定第二个?
  • 好像是*.com/questions/9343594/…的副本

标签: c# .net asynchronous .net-core


【解决方案1】:

1)

var myValue = Task.Run(async () => await MethodAsync()).Result;

异步方法MethodAsync的同步部分将在线程池线程中运行。

2)

var myValue = MethodAsync().Result;

异步方法MethodAsync的同步部分会在调用者的线程中运行。

现在你可能会问,异步方法的同步部分是什么?

同步部分是异步方法中第一个await之前的所有内容。

更准确地说:同步部分是未完成的可等待对象的第一个 await 之前的所有内容。

通常同步部分是微不足道的,但当我们谈论未知的外部 API 时,我们不能 100% 确定。

在调用者线程或线程池线程中运行阻塞代码之间的区别可能并不那么重要。在这两种情况下,调用者的线程都将在异步调用的整个持续时间内被阻塞。第一种方法 (Task.Run) 有什么优势吗?通常添加Task.Run 来解决problems of deadlocks,当awaitWait/Result 混合时很容易出现这种情况。在您的情况下,如果您出于某种原因在内部使用await,或者外部API 在内部使用await 而没有ConfigureAwait(false),则可能会出现此类问题。在这种情况下,您会立即注意到它,并且您可能会修复它。因此,主动使用Task.Run 的好处是让您高枕无忧。缺点是使用线程池线程来运行该方法的同步部分。在大多数情况下,这部分非常小,以微秒为单位,所以如果你走简单的路,你不应该感到内疚。


更新:这是第一种方法的示例,它还演示了外部方法的同步和异步部分:

private void Button1_Click(object sender, EventArgs e)
{
    this.Text = YourMethod();
}

public static int YourMethod()
{
    return Task.Run(async () => await ExternalMethodAsync()).Result;
}

public static async Task<string> ExternalMethodAsync()
{
    Thread.Sleep(500); // Synchronous part
    await Task.Delay(500).ConfigureAwait(false); // Asynchronous part
    return $"Time: {DateTime.Now:HH:mm:ss.fff}";
}

在这种情况下,预防性使用 Task.Run 是多余的,因为外部库遵循使用 ConfigureAwait(false) 等待的良好做法。

这是第二种方法的示例:

public static int YourMethod()
{
    return ExternalMethodAsync().Result;
}

public static async Task<string> ExternalMethodAsync()
{
    Thread.Sleep(500); // Synchronous part
    await Task.Delay(500); // Asynchronous part
    return $"Time: {DateTime.Now:HH:mm:ss.fff}";
}

此代码死锁。如果您直接请求Result,而不使用Task.Run,即使外部库中单个未配置的*await也会导致死锁。

【讨论】:

  • 其实我反编译了API,我要调用的方法只有一行await xxxx,就像代理只是调用另一个方法一样,使用第一种方法有什么好处吗?
  • 其他方法可能还有同步部分。之后可能会等待第三个异步方法,该方法也具有同步部分等。您必须一直反编译才能确切知道发生了什么。
  • 所以我没看懂你的解释,The synchronous part is everything before the first await
  • 你是对的。同步部分不止于此,因为它还包括等待异步方法的同步部分,加上外部等待异步方法中内部等待异步方法的同步部分,以此类推,直到最深的异步方法。
  • 我用一些例子更新了答案,希望能更清楚地区分方法的同步和异步部分。
【解决方案2】:

根据方法的实现方式,您很有可能将线程锁定在第二个实现上。

如果您想安全起见,请使用第一个实现,因为可以保证有一个新线程来处理异步函数,但是这将花费您一些可伸缩性,因为您将使用 2 个线程而不是一个线程。

【讨论】: