我读了这篇文章https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - 但是我看到了一个矛盾:
我意识到UI线程死锁的问题,因为UI线程阻塞等待异步操作完成,但同样的异步操作被同步到UI线程上下文 - 因此异步操作无法进入UI线程,因此UI线程不会停止等待。
文章告诉我们,解决方法是不阻止UI线程,否则你需要在所有地方使用ConfigureAwait(false)
:
您必须在阻塞代码调用的所有方法的传递闭包中使用每个等待,包括所有第三方和第二方代码。
然而,作者稍后在文章中写道:
防止僵局
避免这种情况有两种最佳实践(我的介绍中都包含这些内容):
- 在“库”异步方法中,尽可能使用
ConfigureAwait(false)
。- 不要阻止任务;一直使用
醇>async
。
我在这里看到了一个矛盾 - 在"不要这样做"他写道,必须在任何地方使用ConfigureAwait(false)
都是阻止UI线程的结果 - 但在他的“最佳实践”中#34;然后,他告诉我们这样做:"尽可能使用ConfigureAwait(false)
。" - 虽然我认为"尽可能"会排除第三方代码,但如果没有第三方代码,如果我阻止UI线程,结果是相同的。
至于我的具体问题,这是我在WPF MVVM项目中的当前代码:
private async void ButtonClickEventHandler()
{
WebServiceResponse response = await this.client.PushDinglebopThroughGrumbo();
this.DisplayResponseInUI( response );
}
public class PlumbusWebServiceClient {
private static readonly HttpClient _client = new HttpClient();
public async Task<WebServiceResponse> PushDinglebopThroughGrumbo()
{
try
{
using( HttpResponseMessage response = await _client.GetAsync( ... ) )
{
if( !response.IsSuccessStatusCode ) return WebServiceResponse.FromStatusCode( response.StatusCode );
using( Stream versionsFileStream = await response.Content.ReadAsStreamAsync() )
using( StreamReader rdr = new StreamReader( versionsFileStream ) )
{
return await WebServiceResponse.FromResponse( rdr );
}
}
}
catch( HttpResponseException ex )
{
return WebServiceResponse.FromException( ex );
}
}
}
如果我正确理解了该文档,我应该将ConfigureAwait(false)
添加到每个 await
,而这些 PushDinglebopThroughGrumbo
不在具有需要在UI线程上运行的代码的方法中 - 这是我的WebServiceResponse.FromResponse
方法中的每个方法,也是await StreamReader.ReadLineAsync
中的所有代码(调用await
)。但是,我调用的任何第三方代码如何对StreamReader
执行ConfigureAwait(false)
操作呢?我无法访问他们的源代码,因此这是不可能的。
我不得不将await
放在任何地方 - 我认为awaitfree
关键字的意思是消除明确的任务库调用 - 我不应该有点放弃 - 不应该这样做还有一个不同的关键字,等待恢复 - 上下文免费? (例如(unmodified, same as above)
)。
那么我的代码应该是这样的吗?
public class PlumbusWebServiceClient {
private static readonly HttpClient _client = new HttpClient();
public async Task<WebServiceResponse> PushDinglebopThroughGrumbo()
{
try
{
using( HttpResponseMessage response = await _client.GetAsync( ... ).ConfigureAwait(false) ) // <-- here
{
if( !response.IsSuccessStatusCode ) return WebServiceResponse.FromStatusCode( response.StatusCode );
using( Stream versionsFileStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false) ) // <-- and here
using( StreamReader rdr = new StreamReader( versionsFileStream ) )
{
return await WebServiceResponse.FromResponse( rdr ).ConfigureAwait(false); // <-- and here again, and inside `FromResponse` too
}
}
}
catch( HttpResponseException ex )
{
return WebServiceResponse.FromException( ex );
}
}
}
ConfigureAwait(false)
...我原以为只有在await
方法内的最PlumbusWebServiceClient
次来电即GetAsync
来电时才需要拨打public static ConfiguredTaskAwaitable<T> CF<T>(this Task<T> task) {
return task.ConfigureAwait(false);
}
using( HttpResponseMessage response = await _client.GetAsync( ... ).CF() )
{
...
}
。
如果我确实需要在任何地方应用它,我可以将其简化为扩展方法吗?
class Settings
{
public async Task Export(String fileName)
{
using( StreamWriter wtr = new StreamWriter( fileName, append: false ) )
{
await ExportSetting( wtr, nameof(this.DefaultStatus ), this.DefaultStatus ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ConnectionString ), this.ConnectionString ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TargetSystem ), this.TargetSystem.ToString("G") ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ThemeBase ), this.ThemeBase ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ThemeAccent ), this.ThemeAccent ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ShowSettingsButton), this.ShowSettingsButton ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.ShowActionsColumn ), this.ShowActionsColumn ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.LastNameFirst ), this.LastNameFirst ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TitleCaseCustomers), this.TitleCaseCustomers ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.TitleCaseVehicles ), this.TitleCaseVehicles ? "true" : "false" ).ConfigureAwait(false);
await ExportSetting( wtr, nameof(this.CheckForUpdates ), this.CheckForUpdates ? "true" : "false" ).ConfigureAwait(false);
}
}
private static async Task ExportSetting(TextWriter wtr, String name, String value)
{
String valueEnc = Uri.EscapeDataString( value ); // to encode line-breaks, etc.
await wtr.WriteAsync( name ).ConfigureAwait(false);
await wtr.WriteAsync( '=' ).ConfigureAwait(false);
await wtr.WriteLineAsync( valueEnc ).ConfigureAwait(false);
}
}
......虽然这并没有减轻所有的琐事。
以下是我编写的一些异步代码,它将我的应用程序的设置导出到一个简单的文本文件中 - 我无法帮助,但认为它感觉不对,这是否真的是正确的方法这样做?
POST http://127.0.0.1:8000/comments 500 (Internal Server Error)
答案 0 :(得分:7)
如果我正确理解了该文档,我应该将
ConfigureAwait(false)
添加到每个await
,而await
不在具有需要在UI线程上运行的代码的方法中
是。 UI应用程序中的默认行为是在{0}之后继续在UI线程上执行的代码。当UI线程忙,但您的代码不需要访问UI时,没有必要等待UI线程可用。
(注意:这有意遗漏了一些与此无关的细节。)
但是我调用的任何第三方代码如何对
await
执行StreamReader
操作呢?
只要您通过其他方式避免死锁,这只会影响性能,而不会影响正确性。而第三方代码可能性能不佳的问题并不是一个新问题。
换句话说:遵循两个最佳做法。
我不得不将
ConfigureAwait(false)
放在任何地方 - 我认为await
关键字的意思是消除明确的任务库调用 - 我不应该有点放弃 - 不应该这样做还有一个不同的关键字,等待恢复 - 上下文免费? (例如awaitfree
)。
ConfigureAwait
不是TPL方法。
await
是一般化的,只要它们支持所需的方法,就可以在任意类型上使用它。对于随机示例,您可以为Task
添加扩展方法,以返回允许await
之后的代码在新的专用线程中继续的类型。这不需要带有新关键字的新版本编译器。
但是,它是一个很长的名字。
如果我确实需要在任何地方应用它,我可以将其简化为扩展方法吗?
是的,这完全没问题。
以下是我编写的一些异步代码,它将我的应用程序的设置导出到一个简单的文本文件中 - 我无法帮助,但认为它感觉不对,这是否真的是正确的方法这样做?
正如我在评论中写的那样,我自己也不会使用这种方法......但是如果你真的想这样做,那么你可以在那里获得大量的代码重复,你可以摆脱它们的。随着它的消失,它看起来几乎不再那么糟糕了。
/* SettingsCollection omitted, but trivially implementable using
Dictionary<string, string>, NameValueCollection,
List<KeyValuePair<string, string>>, whatever. */
SettingsCollection GetAllSettings()
{
return new SettingsCollection
{
{ nameof(this.DefaultStatus ), this.DefaultStatus },
{ nameof(this.ConnectionString ), this.ConnectionString },
{ nameof(this.TargetSystem ), this.TargetSystem.ToString("G") },
{ nameof(this.ThemeBase ), this.ThemeBase },
{ nameof(this.ThemeAccent ), this.ThemeAccent },
{ nameof(this.ShowSettingsButton), this.ShowSettingsButton ? "true" : "false" },
{ nameof(this.ShowActionsColumn ), this.ShowActionsColumn ? "true" : "false" },
{ nameof(this.LastNameFirst ), this.LastNameFirst ? "true" : "false" },
{ nameof(this.TitleCaseCustomers), this.TitleCaseCustomers ? "true" : "false" },
{ nameof(this.TitleCaseVehicles ), this.TitleCaseVehicles ? "true" : "false" },
{ nameof(this.CheckForUpdates ), this.CheckForUpdates ? "true" : "false" }
};
}
public async Task Export(String fileName)
{
using( StreamWriter wtr = new StreamWriter( fileName, append: false ) )
foreach (var setting in GetAllSettings())
await ExportSetting( wtr, setting.Key, setting.Value ).ConfigureAwait(false);
}