繁体   English   中英

我应该在每个等待的操作上调用ConfigureAwait(false)

[英]Should I call ConfigureAwait(false) on every awaited operation

我读了这篇文章https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - 但是我看到了一个矛盾:

我知道UI线程死锁的问题,因为UI线程阻塞等待异步操作完成,但同样的异步操作被同步到UI线程上下文 - 因此异步操作无法进入UI线程,所以UI线程不会停止等待。

文章告诉我们解决方法是不阻塞UI线程,否则你需要在任何地方使用ConfigureAwait(false)

您必须在阻塞代码调用的所有方法的传递闭包中使用每个等待,包括所有第三方和第二方代码。

然而,作者后来在文章中写道:

防止死锁
避免这种情况有两个最佳实践(我的介绍帖中都有)

  1. 在“库”异步方法中,尽可能使用ConfigureAwait(false)
  2. 不要阻止任务; 一直使用async

我在这里看到一个矛盾 - 在“不要这样做”部分他写道,必须使用ConfigureAwait(false)到处都是阻塞UI线程的结果 - 但是在他的“最佳实践”列表中他告诉我们这样做:“尽可能使用ConfigureAwait(false) 。” - 虽然我认为“只要有可能”会排除第三方代码,但是在没有第三方代码的情况下,如果我阻止UI线程,结果是相同的。

至于我的具体问题,这是我在WPF MVVM项目中的当前代码:

MainWindowViewModel.cs

private async void ButtonClickEventHandler()
{
    WebServiceResponse response = await this.client.PushDinglebopThroughGrumbo();

    this.DisplayResponseInUI( response );
}

WebServiceClient.cs

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 ,这个方法不在具有需要在UI线程上运行的代码的方法中 - 这是我的PushDinglebopThroughGrumbo方法中的每个方法,还有WebServiceResponse.FromResponse所有代码WebServiceResponse.FromResponse (调用await StreamReader.ReadLineAsync )。 但是我调用的任何第三方代码如何在StreamReader上执行await操作呢? 我将无法访问他们的源代码,因此这是不可能的。

我不得不在任何地方放置ConfigureAwait(false) - 我认为await关键字的意思是消除显式的任务库调用 - 不应该有一个不同的关键字,用于等待resume-context-free然后? (例如awaitfree )。

那么我的代码应该是这样的吗?

MainWindowViewModel.cs

(unmodified, same as above)

WebServiceClient.cs

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)只需要在PlumbusWebServiceClient方法中最顶层的await调用 - 即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);
    }
}

如果我正确理解了文档,我应该将ConfigureAwait(false)添加到每个await ,而不是在具有需要在UI线程上运行的代码的方法中

是。 UI应用程序中的默认行为是await在UI线程上继续之后的代码。 当UI线程忙,但您的代码不需要访问UI时,没有必要等待UI线程可用。

(注意:这有意留下一些与此无关的细节。)

但是我调用的任何第三方代码如何在StreamReader上执行await操作呢?

只要您通过其他方式避免死锁,这只会影响性能,而不会影响正确性。 并且可能性能不佳的第三方代码问题不是新问题。

换句话说:遵循两种最佳实践。

我不得不在任何地方放置ConfigureAwait(false) - 我认为await关键字的意思是消除显式的任务库调用 - 不应该有一个不同的关键字,用于等待resume-context-free然后? (例如awaitfree )。

ConfigureAwait不是TPL方法。

await是一般化的,只要它们支持所需的方法,它就可以用于任意类型。 对于随机示例,您可以为Task添加一个扩展方法,以返回一个类型,该类型允许await后的代码在新的专用线程中继续。 这不需要带有new关键字的新版本编译器。

但是,这是一个很长的名字。

如果我确实需要在任何地方应用它,我可以将其简化为扩展方法吗?

是的,这完全没问题。


这里有一些我编写的异步代码将我的应用程序的设置导出到一个简单的文本文件 - 我不禁觉得它感觉不对,这真的是正确的方法吗?

正如我在评论中所写的那样,我自己也不会使用这种方法......但是如果你真的想这样做,那么你可以在那里找到很多可以摆脱的代码重复。 随着它的消失,它看起来不再那么糟糕了。

/* 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);
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM