C# 我应该在每个等待的操作上调用ConfigureAwait(false)吗
我读了这篇文章,但我发现了一个矛盾: 我知道UI线程死锁的问题,因为UI线程阻塞等待异步操作完成,但同一异步操作与UI线程上下文同步-因此异步操作无法进入UI线程,因此UI线程不会停止等待 这篇文章告诉我们,解决方法是不要在UI线程上阻塞,否则您需要使用C# 我应该在每个等待的操作上调用ConfigureAwait(false)吗,c#,async-await,C#,Async Await,我读了这篇文章,但我发现了一个矛盾: 我知道UI线程死锁的问题,因为UI线程阻塞等待异步操作完成,但同一异步操作与UI线程上下文同步-因此异步操作无法进入UI线程,因此UI线程不会停止等待 这篇文章告诉我们,解决方法是不要在UI线程上阻塞,否则您需要使用ConfigureAwait(false)where: 在阻塞代码调用的所有方法(包括所有第三方和第二方代码)的传递闭包中,必须对每个等待使用 然而,在文章的后面,作者写道: 防止死锁 有两种最佳实践(都在我的介绍文章中介绍)可以避免这种情况:
ConfigureAwait(false)
where:
在阻塞代码调用的所有方法(包括所有第三方和第二方代码)的传递闭包中,必须对每个等待使用
然而,在文章的后面,作者写道:
防止死锁有两种最佳实践(都在我的介绍文章中介绍)可以避免这种情况:
ConfigureAwait(false)
李>
async
ConfigureAwait(false)
将是阻塞UI线程的结果——但在他的“最佳实践”列表中,他告诉我们要这样做:“尽可能使用ConfigureAwait(false)
”“尽可能”将排除第三方代码,但在没有第三方代码的情况下,如果我阻止或不阻止UI线程,结果是相同的
关于我的具体问题,以下是我在WPF MVVM项目中的当前代码:
MainWindowViewModel.cs
WebServiceClient.cs
WebServiceClient.cs
…虽然这并不能减轻所有的烦躁
更新:第二个例子
下面是我编写的一些异步代码,它将我的应用程序的设置导出到一个简单的文本文件中——我忍不住觉得这样做不对,这真的是正确的方法吗
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);
}
}
如果我正确理解了文档,我应该将ConfigureAwait(false)
添加到每个await
方法中不包含需要在UI线程上运行的代码
是。UI应用程序中的默认行为是,在UI线程上继续执行wait
之后的代码。当UI线程繁忙,但您的代码不需要访问UI时,等待UI线程可用是没有意义的
(注意:这有意省略了一些与此无关的细节。)
但是我调用的任何第三方代码在StreamReader
上也执行await
操作,那该怎么办呢
只要您通过其他方式避免死锁,这只会影响性能,而不会影响正确性。而且潜在的性能不佳的第三方代码问题并不是一个新问题
换句话说:遵循这两种最佳实践
我还因为必须到处放置ConfigureAwait(false)
而感到有点不快-我认为await
关键字的目的是消除显式的任务库调用-那么恢复上下文无关的等待不应该有不同的关键字吗?(例如awaitfree
)
ConfigureAwait
不是TPL方法
await
是泛化的,因此它可以用于任意类型,只要它们支持所需的方法。对于随机示例,您可以为任务
添加扩展方法,以返回允许await
之后的代码在新的专用线程中继续的类型。这不需要新版本的使用一个新的关键字编译
但是是的,这是一个很长的名字
如果我确实需要在任何地方应用它,我可以将它简化为一种扩展方法吗
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);
}
}
正如我在评论中所写的,我自己根本不会使用这种方法……但是如果你真的想,你已经有很多代码重复,你可以去掉它们。随着它们的消失,它看起来不再那么糟糕了
/* 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);
}
/*设置集合省略,但可以使用
字典、NameValueCollection、,
名单,随便什么*/
设置集合GetAllSettings()
{
返回新设置集合
{
{nameof(this.DefaultStatus),this.DefaultStatus},
{nameof(this.ConnectionString),this.ConnectionString},
{nameof(this.TargetSystem),this.TargetSystem.ToString(“G”)},
{nameof(this.ThemeBase),this.ThemeBase},
{nameof(this.themeacent),this.themeacent},
{nameof(this.ShowSettingsButton)、this.ShowSettingsButton?“true”:“false”},
{nameof(this.ShowActionsColumn),this.ShowActionsColumn?“true”:“false”},
{nameof(this.LastNameFirst),this.LastNameFirst?“真”:“假”},
{nameof(this.TitleCaseCustomers),this.TitleCaseCustomers?“true”:“false”},
{nameof(this.TitleCaseVehicles),this.TitleCaseVehicles?“真”:“假”},
{nameof(this.CheckForUpdates),this.CheckForUpdates?“true”:“false”}
};
}
公共异步任务导出(字符串文件名)
{
使用(StreamWriter wtr=newstreamwriter(文件名,附加:false))
foreach(GetAllSettings()中的var设置)
等待导出设置(wtr,setting.Key,setting.Value)。配置等待(false);
}
@PeterDuniho我为不清楚表示歉意。我已经更新了开头的段落,使之更准确。我不认为我误解了这篇文章,我只是想澄清一下。@PeterDuni
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);
}
}
/* 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);
}