为什么调试时我的异步代码同步运行? [英] Why Does My Asynchronous Code Run Synchronously When Debugging?

查看:121
本文介绍了为什么调试时我的异步代码同步运行?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用异步功能实现称为ReadAllLinesAsync的方法.我产生了以下代码:

I am trying to implement a method called ReadAllLinesAsync using the async feature. I have produced the following code:

private static async Task<IEnumerable<string>> FileReadAllLinesAsync(string path)
{
    using (var reader = new StreamReader(path))
    {
        while ((await reader.ReadLineAsync()) != null)
        {

        }
    }
    return null;
}

private static void Main()
{
    Button buttonLoad = new Button { Text = "Load File" };
    buttonLoad.Click += async delegate
    {
        await FileReadAllLinesAsync("test.txt"); //100mb file!
        MessageBox.Show("Complete!");
    };

    Form mainForm = new Form();
    mainForm.Controls.Add(buttonLoad);
    Application.Run(mainForm);
}

我希望列出的代码能够异步运行,事实上,它确实可以运行!但是仅当我在没有的情况下运行代码时,才运行Visual Studio调试器.

I expect the listed code to run asynchronously and as a matter of fact, it does! But only when I run the code without the Visual Studio Debugger.

运行带有 附加代码的代码时,代码将同步运行,从而阻塞了主线程,导致UI挂起.

When I run the code with the Visual Studio Debugger attached, the code runs synchronously, blocking the main thread causing the UI to hang.

我尝试并成功在三台机器上重现该问题.每个测试都是使用Visual Studio 2012在64位计算机(Windows 8或Windows 7)上进行的.

I have attempted and succeeded to reproduce the problem on three machines. Each test was conducted on a 64bit machine (either Windows 8 or Windows 7) using Visual Studio 2012.

我想知道为什么会发生此问题以及如何解决(因为在没有调试器的情况下运行可能会阻碍开发).

I would like to know why this problem is occuring and how to solve it (as running without the debugger will likely hinder development).

推荐答案

我在某种程度上看到了与您相同的问题-但仅在某种程度上.对我而言,UI在调试器中非常不稳定,有时在调试器中非常不稳定. (顺便说一下,我的文件由很多行组成,每行10个字符-数据的形状将在此处更改行为.)在调试器中,开始时通常比较好,然后很长时间都不好,那么有时它会恢复.

I'm seeing the same problem as you to an extent - but only to an extent. For me, the UI is very jerky in the debugger, and occasionally jerky not in the debugger. (My file consists of lots of lines of 10 characters, by the way - the shape of the data will change behaviour here.) Often in the debugger it's good to start with, then bad for a long time, then it sometimes recovers.

怀疑,问题可能仅在于磁盘太快而行太短.我知道这听起来很疯狂,所以让我解释一下...

I suspect the problem may simply be that your disk is too fast and your lines are too short. I know that sounds crazy, so let me explain...

在使用await表达式时,如果需要,它将仅通过 路径进行附加延续"路径.如果结果已经存在,则代码将提取值并在同一线程中继续.

When you use an await expression, that will only go through the "attach a continuation" path if it needs to. If the results are present already, the code just extracts the value and continues in the same thread.

这意味着,如果ReadLineAsync始终返回在返回时已完成的任务,则您将有效地看到同步行为. ReadLineAsync很有可能查看它已经被缓冲的数据,并尝试同步地找到其中的一行作为开始.然后,操作系统很可能会从磁盘读取更多数据,以便为您的应用程序使用做好准备……这意味着UI线程再也没有机会抽出其正常消息,因此UI冻结了.

That means, if ReadLineAsync always returns a task which is completed by the time it returns, you'll effectively see synchronous behaviour. It's entirely possible that ReadLineAsync looks at what data it's already got buffered, and tries to synchronously find a line within it to start with. The operating system may well then read more data from the disk so that it's ready for your application to use... which means that the UI thread never gets a chance to pump its normal messages, so the UI freezes.

我曾预期,认为通过网络运行相同的代码可以解决"该问题,但事实并非如此. (请注意,它确实改变了混蛋的显示方式.)但是,使用:

I had expected that running the same code over a network would "fix" the problem, but it didn't seem to. (It changes exactly how the jerkiness is shown, mind you.) However, using:

await Task.Delay(1);

执行取消冻结用户界面. (尽管Task.Yield并没有,这再次让我感到困惑.我怀疑这可能是继续和其他UI事件之间的优先级问题.)

Does unfreeze the UI. (Task.Yield doesn't though, which again confuses me a lot. I suspect that may be a matter of prioritization between the continuation and other UI events.)

现在,为什么您只在调试器中看到它-仍然使我感到困惑.也许与调试器中如何处理中断,巧妙地改变时序有关.

Now as for why you're only seeing this in the debugger - that still confuses me. Perhaps it's something to do with how interrupts are processed in the debugger, changing the timing subtly.

这些只是猜测,但至少是一些受过教育的猜测.

These are only guesses, but they're at least somewhat educated ones.

好的,我已经找到一种方法来表明至少要部分可以做到这一点.像这样更改您的方法:

Okay, I've worked out a way to indicate that it's at least partly to do with that. Change your method like this:

private static async Task<IEnumerable<string>>
    FileReadAllLinesAsync(string path, Label label)
{
    int completeCount = 0;
    int incompleteCount = 0;
    using (var reader = new StreamReader(path))
    {
        while (true)
        {
            var task = reader.ReadLineAsync();
            if (task.IsCompleted)
            {
                completeCount++;
            }
            else
            {
                incompleteCount++;
            }
            if (await task == null)
            {
                break;
            }
            label.Text = string.Format("{0} / {1}",
                                       completeCount,
                                       incompleteCount);
        }
    }
    return null;
}

...,然后创建一个合适的标签并将其添加到UI.在我的机器上,无论是在调试状态还是非调试状态,我都看到 far 命中率比"incomplete"命中率更高-奇怪的是,在调试器而不是.因此,只有在阅读了大约每85行中的一行后,UI 才有机会进行更新.您应该在计算机上尝试同样的方法.

... and create and add a suitable label to the UI. On my machine, both in debug and non-debug, I see far more "complete" hits than "incomplete" - oddly enough, the ratio of complete to incomplete is 84:1 consistently, both under the debugger and not. So it's only after reading about one in 85 lines that the UI can get a chance to update. You should try the same on your machine.

作为另一项测试,我在label.Paint事件中添加了一个计数器递增-在调试器中,对于相同数量的行,它执行的次数是调试器中执行次数的1/10,而不是调试器中执行次数的不到1/10.

As another test, I added a counter incrementing in the label.Paint event - in the debugger it only executed 1/10th as many times as not in the debugger, for the same number of lines.

这篇关于为什么调试时我的异步代码同步运行?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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