从 Sync 方法调用 Async 方法的最佳做法是什么?
What is the best practice to call Async method from Sync method?
我知道有些人会争论“为什么不直接将 SyncMethod() 设为异步方法?”。我们希望,但在现实世界中,有时出于向后兼容的原因,我们必须保持 SyncMethod 的原样。
情况是这样的。我们遇到了以下代码的死锁。
public void SyncMethod()
{
var serviceResult = ProcessDataAsync().Result;
}
public await ServiceResult ProcessDataAsync()
{
//Do other things
await GetDataFromApiAsync();
}
private static HttpClient Client = new HttpClient();
public await ApiResult GetDataFromApiAsync()
{
var response = await Client.GetAsync("http://api.com/getJson");
dynamic obj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
return obj;
}
选项 1:使用 Task.Run 并得到 task.Result。这解决了死锁问题,但它被迫 运行 在原始线程的同步上下文之外的新线程中。但是,在某些环境中这是非常不明智的:尤其是 Web 应用程序。这是一个好习惯吗?
public void SyncMethod()
{
Task<decimal> task = Task.Run<decimal>(async () => await ProcessDataAsync());
var serviceResult = task.Result;
}
Optoin 2:从 Sync 一直添加 ConfigureAwait(false) 到最后一个异步方法。
public void SyncMethod()
{
var serviceResult = GetDataFromApiAsync().ConfigureAwait(false).GetAwaiter().GetResult();;
}
public await ServiceResult ProcessDataAsync()
{
//Do other things
await GetDataFromApiAsync().ConfigureAwait(false);
}
private static HttpClient Client = new HttpClient();
public await ApiResult GetDataFromApiAsync()
{
var response = await Client.GetAsync("http://api.com/getJson").ConfigureAwait(false);
dynamic obj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return obj;
}
选项 3:Stephen Cleary's answer。
public void SyncMethod()
{
var serviceResult = GetDataFromApiAsync().(sync: true).GetAwaiter().GetResult();
}
所有这些解决方案都将解决死锁问题。我的问题是最佳做法是什么?
what the best practice would be?
没有最佳实践,原因如下:
每个 hack 在某些情况下有效,而在其他情况下无效。没有适用于所有情况的 hack。如果有一种适用于任何地方的 hack,那么每个人都会使用它,而这种 hack 将是最佳实践。但是没有一种适用于任何地方的黑客,所以没有“最佳实践”。 没有通用解决方案。
various hacks are described in this article,以及对每种情况有效和无效的情况的描述。
我知道有些人会争论“为什么不直接将 SyncMethod() 设为异步方法?”。我们希望,但在现实世界中,有时出于向后兼容的原因,我们必须保持 SyncMethod 的原样。
情况是这样的。我们遇到了以下代码的死锁。
public void SyncMethod()
{
var serviceResult = ProcessDataAsync().Result;
}
public await ServiceResult ProcessDataAsync()
{
//Do other things
await GetDataFromApiAsync();
}
private static HttpClient Client = new HttpClient();
public await ApiResult GetDataFromApiAsync()
{
var response = await Client.GetAsync("http://api.com/getJson");
dynamic obj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync());
return obj;
}
选项 1:使用 Task.Run 并得到 task.Result。这解决了死锁问题,但它被迫 运行 在原始线程的同步上下文之外的新线程中。但是,在某些环境中这是非常不明智的:尤其是 Web 应用程序。这是一个好习惯吗?
public void SyncMethod()
{
Task<decimal> task = Task.Run<decimal>(async () => await ProcessDataAsync());
var serviceResult = task.Result;
}
Optoin 2:从 Sync 一直添加 ConfigureAwait(false) 到最后一个异步方法。
public void SyncMethod()
{
var serviceResult = GetDataFromApiAsync().ConfigureAwait(false).GetAwaiter().GetResult();;
}
public await ServiceResult ProcessDataAsync()
{
//Do other things
await GetDataFromApiAsync().ConfigureAwait(false);
}
private static HttpClient Client = new HttpClient();
public await ApiResult GetDataFromApiAsync()
{
var response = await Client.GetAsync("http://api.com/getJson").ConfigureAwait(false);
dynamic obj = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync().ConfigureAwait(false));
return obj;
}
选项 3:Stephen Cleary's answer。
public void SyncMethod()
{
var serviceResult = GetDataFromApiAsync().(sync: true).GetAwaiter().GetResult();
}
所有这些解决方案都将解决死锁问题。我的问题是最佳做法是什么?
what the best practice would be?
没有最佳实践,原因如下:
每个 hack 在某些情况下有效,而在其他情况下无效。没有适用于所有情况的 hack。如果有一种适用于任何地方的 hack,那么每个人都会使用它,而这种 hack 将是最佳实践。但是没有一种适用于任何地方的黑客,所以没有“最佳实践”。 没有通用解决方案。
various hacks are described in this article,以及对每种情况有效和无效的情况的描述。