RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 681382
Accepted
Lightness
Lightness
Asked:2020-06-20 15:36:15 +0000 UTC2020-06-20 15:36:15 +0000 UTC 2020-06-20 15:36:15 +0000 UTC

使用 ConfigureAwait(false)

  • 772

我看一个代码示例。我很惊讶它是先ConfigureAwait(false)调用 onhttpClient.GetStringAsync然后 on sourceStream.WriteAsync。据我所知ConfigureAwait(false),它表明代码应该继续执行而不是在上下文中UI,而是在任务的上下文中。为什么然后调用它 2 次?

private async void Button_Click(object sender, RoutedEventArgs e)
{
    HttpClient httpClient = new HttpClient();
    //до этого момента всё выполняется в UI контексте?
    string content = await httpClient.GetStringAsync("http://www.microsoft.com").
        ConfigureAwait(false); 
    //после выполнения верхней строчки остальной код который внизу будет выполняться в контексте веррхнего таска?
    using (FileStream sourceStream = new FileStream("temp.html", FileMode.Create, 
        FileAccess.Write, FileShare.None, 4096, useAsync: true))
    {
        byte[] encodedText = Encoding.Unicode.GetBytes(content);
        await sourceStream.WriteAsync(encodedText, 0, encodedText.Length).
            ConfigureAwait(false);
       //будь дальше какой-то код, в контексте какого потока он выполнялся б?
    };
}
c#
  • 5 5 个回答
  • 10 Views

5 个回答

  • Voted
  1. Best Answer
    VladD
    2020-06-20T18:17:11Z2020-06-20T18:17:11Z

    看。

    ConfigureAwait(false)确实意味着“我不在乎什么流动 SynchronizationContext'方法的尾部将不会被执行。'

    也就是说,第一个ConfigureAwait(false)可以将方法的“尾部”发送到后台线程。但究竟什么可以,什么不能!如果由于某种原因第一个任务被同步执行(例如,该行已经在缓存中),那么将不会SynchronizationContext执行到另一个任务的传输,并会在原始上下文中继续执行。

    如果同时第二await个没有配备 construct ConfigureAwait(false),那么方法的尾部将在原始上下文中再次执行 - 也就是说,在您的情况下,在 UI 的上下文中。

    因此,对于不与 UI 通信的库方法,实际上有必要添加一个.awaitConfigureAwait(false)


    很明显,添加到每个的await有点ConfigureAwait(false)太懒了。你可以改用这个技巧:一开始就“逃”到线程池,就不用再担心了。这可以通过以下构造来完成:

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        await AsyncHelper.RedirectToThreadPool();
        // всё, мы больше не в UI-контексте, гарантировано
    
        HttpClient httpClient = new HttpClient();
        string content = await httpClient.GetStringAsync("http://www.microsoft.com"); 
        // ...
    }
    

    助手类(取自此处):

    static class AsyncHelper
    {
        public static ThreadPoolRedirector RedirectToThreadPool() =>
            new ThreadPoolRedirector();
    }
    
    public struct ThreadPoolRedirector : INotifyCompletion
    {
        // awaiter и awaitable в одном флаконе
        public ThreadPoolRedirector GetAwaiter() => this;
    
        // true означает выполнять продолжение немедленно 
        public bool IsCompleted => Thread.CurrentThread.IsThreadPoolThread;
    
        public void OnCompleted(Action continuation) =>
            ThreadPool.QueueUserWorkItem(o => continuation());
    
        public void GetResult() { }
    }
    

    (取自Stephen Toub await anything;的想法)


    来自同一 Stephen Toub 的关于该主题的额外阅读:ConfigureAwait FAQ(和俄语翻译)——感谢@aepot的提示。

    • 31
  2. sp7
    2020-06-20T17:24:21Z2020-06-20T17:24:21Z

    一点理论:

    使用关键字时,await编译器会做很多有趣的事情,但在这种情况下,我们感兴趣的是记住(实际上,其他上下文也会记住)同步上下文SynchronizationContext,它旨在在特定的线程中执行代码类型。该类SynchronizationContext有一个重要的方法Post,可确保传递的委托将在正确的上下文中执行。

    现在,我们记得第await一个代码之前的代码是在调用线程上执行的,但是当您的方法在 之后恢复时会发生什么await?事实上,在大多数情况下,它也在调用线程上执行,即使调用线程可能在此期间做了其​​他事情。为了达到这个效果,当前的上下文被SynchronizationContext 保留(当遇到运算符时会发生这种情况await)。接下来,当方法恢复时,编译器插入一个调用Post,以便在记住的上下文中恢复执行。通常,调用此方法的成本相对较高。因此,为了避免开销,.NET不调用Post如果记住的同步上下文与任务完成时的当前同步上下文匹配。但是,如果同步上下文不同,那么昂贵的Post. 如果性能是重中之重,或者如果您处理的库代码不关心它在哪个线程上运行,那么承担费用可能没有意义。ConigureAwait(false)因此,在这种情况下,您应该在等待之前调用该方法。重要的是要了解此方法旨在通知.NET您不关心在哪个线程上恢复执行。如果这个线程不是很重要,比如从池中取出来,那么里面的代码就会继续执行。但是如果线程出于某种原因很重要,那么.NET将更愿意为其他事情释放它,并在从池中获取的线程中继续执行您的方法。关于线程是否重要的​​决定是基于对当前同步上下文的分析做出的。


    这是介绍性的,现在让我们稍微现代化您的示例。我们会将负责从站点接收内容的功能www.microsoft.com移到一个单独的方法中。请注意,ConigureAwait(false)这里不再使用它。

     public async Task<string> GetContentAsync()
     {
         HttpClient httpClient = new HttpClient();
         string content = await httpClient.GetStringAsync("http://www.microsoft.com");
         return content;
     }
    

    接下来,让我们稍微更改一下点击事件处理程序:

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        // Обратите внимание, что здесь мы не используем оператор `await`
        // Кроме того, все что идет ниже, нам уже не интересно, так как мы попали в deadlock
        var content = GetContentAsync().Result;
    
        using (FileStream sourceStream = new FileStream("temp.html", FileMode.Create, 
        FileAccess.Write, FileShare.None, 4096, useAsync: true))
        {
            byte[] encodedText = Encoding.Unicode.GetBytes(content);
            await sourceStream.WriteAsync(encodedText, 0, encodedText.Length).
            ConfigureAwait(false);
        };
    }
    

    这里发生了什么以及为什么会发生死锁。

    1. 属性调用Result会阻塞调用线程,直到异步操作GetContentAsync完成。

    2. 所以在GetContentAsync使用关键字的方法中,本例中的当前上下文await将被保存。SynchronizationContextUI

    3. 方法GetContentAsync执行后,需要Button_Click在保存的上下文中恢复方法的工作SynchronizationContext,但这行不通,因为 主线程因为调用Result.

    实际总结:

    如果性能是重中之重,或者如果您正在谈论不关心它在哪个线程上运行的库代码ConigureAwait(false),您应该使用.

    • 17
  3. Vadim Ovchinnikov
    2020-06-20T15:42:17Z2020-06-20T15:42:17Z

    后续调用ConfigureAwait(false)不会以任何方式影响同步上下文。该方法仍然在 UI 线程上运行。

    但是我在我的代码中做同样的事情。这通常是以礼貌的方式完成的。因此,如果await删除其中一个 - 构造,该方法不会中断。

    • 6
  4. Serginio
    2020-06-21T21:29:17Z2020-06-21T21:29:17Z

    为了更容易使用 ConfigureAwait(false) 您可以使用

    Fody 配置等待

    你的代码

    using Fody;
    
    [ConfigureAwait(false)]
    public class MyAsyncLibrary
    {
        public async Task MyMethodAsync()
        {
            await Task.Delay(10);
            await Task.Delay(20);
        }
    
        public async Task AnotherMethodAsync()
        {
            await Task.Delay(30);
        }
    }
    

    什么被编译

    public class MyAsyncLibrary
    {
        public async Task MyMethodAsync()
        {
            await Task.Delay(10).ConfigureAwait(false);
            await Task.Delay(20).ConfigureAwait(false);
        }
    
        public async Task AnotherMethodAsync()
        {
            await Task.Delay(30).ConfigureAwait(false);
        }
    }
    
    • 3
  5. Artem G
    2020-09-10T17:17:51Z2020-09-10T17:17:51Z

    在这个例子中,只有一个有任务经验的程序员使用了一种过时的等待方法:

    private Task<string> ReadFileAsync() // no async key word. Need to warry about context and threads and use ConfigureAwait(false)
    {
        return Task.Run(() => 
        {
            //some async work
        });
    }
    //... 
    Task<string> result = ReadFileAsync.ConfigureAwait(false);
    

    很可能是误导了两种方法的混合。您示例中的 ConfigureAwait 方法根本不需要,它根本不起作用,因为 已经有 await 将为其生成一个单独的异步包装器,该方法将返回 Task ,然后才等待结果。

    这一切都会返回给调用异步任务的线程。仅当调用线程当时正在执行其他一些长时间运行的任务时,才有可能出现此问题。然后,为了节省时间,我们会使用 ConfigureAwait,这听起来像是“已经返回?占用任何空闲线程,忘记上下文并继续工作”,但这在此类代码中基本上是不可能的。实际上有 1 个任务,我们正在等待它完成。

    你的代码应该是这样的:

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
        HttpClient httpClient = new HttpClient();
        string content = await httpClient.GetStringAsync("http://www.microsoft.com");
    
        using (FileStream sourceStream = new FileStream("temp.html", FileMode.Create, 
            FileAccess.Write, FileShare.None, 4096, useAsync: true))
        {
            byte[] encodedText = Encoding.Unicode.GetBytes(content);
            await sourceStream.WriteAsync(encodedText, 0, encodedText.Length);
            // весь код после WriteAsync выполняется в контексте UI, но на время, пока будет выполняться асинхронный код WriteAsync, поток UI благодаря await не будет ждать ответа (не будет зависания) и вернётся только после завершения асинхронной задачи (await). Внутри компилятором будет сгенерирован код, который это и сделает.
        };
    }
    

    这是 Microsoft 考试 70-843 书中第 26 页中描述的类似情况。这是比 Internet 上的文章可靠得多的来源,其中所有内容都收集在堆中以及 async\await 和 ConfigureAwait() 中:

    private async void StartButton_Click(object sender, RoutedEventArgs e)
    {
      long noOfValues = long.Parse(NumberOfValuesTextBox.Text);
      ResultTextBlock.Text = "Calculating";
      double result = await (asyncComputeAverages(noOfValues));
      ResultTextBlock.Text = "Result: " + result.ToString();
    }
    

    关键字先于方法调用,该方法将返回要执行的任务。编译器将生成代码,使异步方法在到达 await 时返回给调用者。然后它将继续生成代码,这些代码将异步执行等待的操作,然后继续异步方法的主体。对于清单 1-32 中的 Button_Click 方法,这意味着结果会在 asyncComputeAverages 返回的任务完成时显示。代码在运行时不会阻塞用户界面,显示在正确的线程(原始事件处理程序)上更新,使用起来非常简单。

    或者在哈布雷

    聚苯乙烯

    我将尝试用教练、足球运动员和球的例子来解释。球在哪里流动。教练正在与球员交谈(同步执行)。教练可以踢球然后说跑。所以他可以为所有 11 名球员提供 11 个球流。但在您的代码中,它执行以下操作:

    1. 说
    2. 踢球。
    3. 等着球员拿来,自己忙着其他事情,不接球。
    4. 进一步讨论
    5. 然后将球踢向另一个方向。

    这种情况需要 ConfigureAwait(false) :

    1. 教练正在和第一位球员交谈
    2. 踢球,第一个球员跑开,拿走了流球
    3. 教练正在和第二名球员交谈
    4. 这时,第一只跑了过来,等待新的打击。但教练仍在与第二名球员交谈。
    5. 因此,在 ConfigureAwait(false) 的情况下,球员拿走任何自由球,并且在不分散教练注意力的情况下继续训练(击球并跑开)。因为上下文(教练的指示)对他来说不再重要,或者他把一切都写在一张纸上(他保存了上下文)
    • 0

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    Python 3.6 - 安装 MySQL (Windows)

    • 1 个回答
  • Marko Smith

    C++ 编写程序“计算单个岛屿”。填充一个二维数组 12x12 0 和 1

    • 2 个回答
  • Marko Smith

    返回指针的函数

    • 1 个回答
  • Marko Smith

    我使用 django 管理面板添加图像,但它没有显示

    • 1 个回答
  • Marko Smith

    这些条目是什么意思,它们的完整等效项是什么样的

    • 2 个回答
  • Marko Smith

    浏览器仍然缓存文件数据

    • 1 个回答
  • Marko Smith

    在 Excel VBA 中激活工作表的问题

    • 3 个回答
  • Marko Smith

    为什么内置类型中包含复数而小数不包含?

    • 2 个回答
  • Marko Smith

    获得唯一途径

    • 3 个回答
  • Marko Smith

    告诉我一个像幻灯片一样创建滚动的库

    • 1 个回答
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Алексей Шиманский 如何以及通过什么方式来查找 Javascript 代码中的错误? 2020-08-03 00:21:37 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    user207618 Codegolf——组合选择算法的实现 2020-10-23 18:46:29 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5