我应该在每个等待的操作上调用ConfigureAwait(false)吗? [英] Should I call ConfigureAwait(false) on every awaited operation

查看:47
本文介绍了我应该在每个等待的操作上调用ConfigureAwait(false)吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I read this article https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html - however I'm seeing a contradiction:

我知道死锁UI线程的问题,因为UI线程阻塞了等待异步操作完成的时间,但是同一异步操作已同步到UI线程上下文-因此,异步操作无法进入UI线程,因此UI线程不会停止等待.

I'm aware of the problem of deadlocking the UI thread because the UI thread blocks waiting for an async operation to complete, but the same async operation is synchronized to the UI thread context - consequently the async operation cannot enter the UI thread, so the UI thread won't stop waiting.

本文告诉我们解决方法是不要在UI线程上阻塞,否则您需要使用ConfigureAwait(false) 无处不在:

The article tells us the workaround is to not block on the UI thread, otherwise you need to use ConfigureAwait(false) everywhere:

在传递阻塞中,阻塞代码调用的所有方法(包括所有第三方代码和第二方代码)都必须用于每次等待.

You would have to use for every await in the transitive closure of all methods called by the blocking code, including all third- and second-party code.

但是在文章的后面,作者写道:

However later on in the article the author writes:

防止死锁
有两种避免这种情况的最佳做法(在我的介绍性文章中都有介绍):

Preventing the Deadlock
There are two best practices (both covered in my intro post) that avoid this situation:

  1. 在库"异步方法中,尽可能使用ConfigureAwait(false).
  2. 不要阻止任务;一直使用async.
  1. In your "library" async methods, use ConfigureAwait(false) wherever possible.
  2. Don’t block on Tasks; use async all the way down.

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

I'm seeing a contradiction here - in the "don't do this" section he writes that having to use ConfigureAwait(false) everywhere would be the consequence of blocking the UI thread - but in his "best practices" list he then tells us to do just that: "use ConfigureAwait(false) wherever possible." - though I suppose "wherever possible" would exclude third-party code, but in the case where there is no third-party code the result is the same if I block the UI thread or not.

对于我的特定问题,这是我在WPF MVVM项目中的当前代码:

As for my specific problem, here is my current code in a WPF MVVM project:

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中的所有代码(调用await StreamReader.ReadLineAsync).但是,我调用的还会在StreamReader上执行await操作的任何第三方代码呢?我将无法访问他们的源代码,所以那是不可能的.

If I understand the document correctly, I should add ConfigureAwait(false) to every await that is not in a method that has code that needs to run on the UI thread - which is every method inside my PushDinglebopThroughGrumbo method, but also all code in WebServiceResponse.FromResponse (which calls await StreamReader.ReadLineAsync). But what about any third-party code I call which also performs await operations on the StreamReader? I won't have access to their source-code so that would be impossible.

我也不得不推迟在任何地方放置ConfigureAwait(false)的工作-我认为await关键字的重点是消除明确的任务库调用-不应为简历使用其他关键字-那么没有上下文的等待吗? (例如awaitfree).

I'm also a bit put-off by having to place ConfigureAwait(false) everywhere - I thought the point of the await keyword was to eliminate explicit task library calls - shouldn't there be a different keyword for resume-context-free awaiting then? (e.g. awaitfree).

那么我的代码应该看起来像这样吗?

So should my code then look like this?

(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 );
        }
    }
}

...我本以为仅在PlumbusWebServiceClient方法内最顶层的await调用上(即GetAsync调用)才需要调用ConfigureAwait(false).

...I would have thought that calling ConfigureAwait(false) would only be necessary on the topmost await call inside the PlumbusWebServiceClient method - i.e. the GetAsync call.

如果我确实需要将其应用于所有地方,是否可以将其简化为扩展方法?

If I do need to apply it everywhere, could I simplify it to an extension method?

public static ConfiguredTaskAwaitable<T> CF<T>(this Task<T> task) {
    return task.ConfigureAwait(false);
}

using( HttpResponseMessage response = await _client.GetAsync( ... ).CF() )
{
    ...
}

...尽管这并不能减轻所有的困扰.

...though this doesn't alleviate all of the fiddliness.

这是我编写的一些异步代码,可将我的应用程序设置导出到一个简单的文本文件中-我忍不住觉得它不合适,这真的是正确的方法吗?

Here is some async code I wrote that exports my application's settings to a simple text file - I can't help but think it doesn't feel right, is this really the correct way to do this?

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)添加到不在具有需要在UI线程上运行的代码的方法中的每个await

If I understand the document correctly, I should add ConfigureAwait(false) to every await that is not in a method that has code that needs to run on the UI thread

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

Yes. The default behaviour in UI applications is for code after await to continue on the UI thread. When the UI thread is busy, but your code does not need to access the UI, there is no point in waiting for the UI thread to become available.

(注意:此处故意遗漏了一些与此处无关的细节.)

(Note: this intentionally leaves out some details not relevant here.)

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

But what about any third-party code I call which also performs await operations on the StreamReader?

只要避免通过其他方式造成死锁,这只会影响性能,而不会影响正确性.而且,可能无法很好地执行第三方代码的问题也不是新问题.

So long as you avoid deadlocks through other means, this will only affect performance, not correctness. And the problem of potentially poorly performing third party code is not a new problem.

换句话说:遵循两个最佳实践.

In other words: follow both best practices.

我也不得不推迟在任何地方放置ConfigureAwait(false)的工作-我认为await关键字的重点是消除显式的任务库调用-不应为简历使用其他关键字-那么没有上下文的等待吗? (例如awaitfree).

I'm also a bit put-off by having to place ConfigureAwait(false) everywhere - I thought the point of the await keyword was to eliminate explicit task library calls - shouldn't there be a different keyword for resume-context-free awaiting then? (e.g. awaitfree).

ConfigureAwait不是TPL方法.

await是通用的,因此可以在任意类型上使用,只要它们支持所需的方法即可.对于一个随机示例,您可以为Task添加扩展方法以返回一种类型,该类型允许await之后的代码在新的专用线程中继续.不需要使用新关键字的新版本编译器.

await is generalised so that it can be used on arbitrary types so long as they support the required methods. For a random example, you might add an extension method for a Task to return a type that allows the code after await to continue in a new dedicated thread. This would not require a new version of the compiler with a new keyword.

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

But yes, it's a long name.

如果我确实需要将其应用于所有地方,是否可以将其简化为扩展方法?

If I do need to apply it everywhere, could I simplify it to an extension method?

是的,那很好.

这是我编写的一些异步代码,可将我的应用程序设置导出到一个简单的文本文件中-我忍不住觉得它不合适,这真的是正确的方法吗?

Here is some async code I wrote that exports my application's settings to a simple text file - I can't help but think it doesn't feel right, is this really the correct way to do this?

正如我在评论中所写的那样,我自己不会使用这种方法...但是,如果您愿意,那里有很多可以避免的代码重复.有了它,它看起来就不再那么糟糕了.

As I wrote in the comments, I wouldn't use that approach at all myself... but if you do want to, you've got a lot of code duplication in there that you can get rid of. And with that gone, it doesn't look nearly as bad any more.

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

这篇关于我应该在每个等待的操作上调用ConfigureAwait(false)吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆