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

C# 我应该在每个等待的操作上调用ConfigureAwait(false)吗,c#,async-await,C#,Async Await,我读了这篇文章,但我发现了一个矛盾: 我知道UI线程死锁的问题,因为UI线程阻塞等待异步操作完成,但同一异步操作与UI线程上下文同步-因此异步操作无法进入UI线程,因此UI线程不会停止等待 这篇文章告诉我们,解决方法是不要在UI线程上阻塞,否则您需要使用ConfigureAwait(false)where: 在阻塞代码调用的所有方法(包括所有第三方和第二方代码)的传递闭包中,必须对每个等待使用 然而,在文章的后面,作者写道: 防止死锁 有两种最佳实践(都在我的介绍文章中介绍)可以避免这种情况:

我读了这篇文章,但我发现了一个矛盾:

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

这篇文章告诉我们,解决方法是不要在UI线程上阻塞,否则您需要使用
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);
    }