我正在尝试使用异步功能实现一个名为 ReadAllLinesAsync
的方法。我已经生成了以下代码:
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 调试器运行代码时。
当我附加 Visual Studio 调试器运行代码时,代码同步运行,阻塞主线程导致 UI 挂起。
我已经尝试并成功地在三台机器上重现了这个问题。每个测试都是在 64 位机器(Windows 8 或 Windows 7)上使用 Visual Studio 2012 进行的。
我想知道为什么会出现这个问题以及如何解决它(因为在没有调试器的情况下运行可能会阻碍开发)。
最佳答案
问题是您在一个什么都不做的紧密循环中调用 await reader.ReadLineAsync()
- 除了在每次等待之后将执行返回到 UI 线程,然后再重新开始。仅当 ReadLineAsync()
尝试读取一行时,您的 UI 线程才可以自由处理 Windows 事件。
要解决此问题,您可以将调用更改为 await reader.ReadLineAsync().ConfigureAwait(false)
。
await
等待异步调用完成并将执行返回到首先调用 await
的同步上下文。在桌面应用程序中,这是 UI 线程。这是一件好事,因为它允许您直接更新 UI,但如果您在 await
之后立即处理异步调用的结果,可能会导致阻塞。
您可以通过指定 ConfigureAwait(false)
来更改此行为,在这种情况下,执行将在不同的线程中继续,而不是在原始同步上下文中。
即使不是紧密循环,您的原始代码也会阻塞,因为循环中处理数据的任何代码仍会在 UI 线程中执行。要在不添加 ConfigureAwait
的情况下异步处理数据,您应该在使用例如创建的任务中处理数据。 Task.Factory.StartNew 并等待该任务。
以下代码不会阻塞,因为处理是在不同的线程中完成的,允许 UI 线程处理事件:
while ((line= await reader.ReadLineAsync()) != null)
{
await Task.Factory.StartNew(ln =>
{
var lower = (ln as string).ToLowerInvariant();
Console.WriteLine(lower);
},line);
}
关于c# - 为什么我的异步代码调试的时候是同步运行的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17507374/