Use*_*678 12 c# asynchronous visual-studio async-await
我正在尝试实现一个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);
}
Run Code Online (Sandbox Code Playgroud)
我希望列出的代码能够异步运行,事实上,确实如此!但只有在没有 Visual Studio Debugger的情况下运行代码时.
当我附加Visual Studio Debugger 运行代码时,代码同步运行,阻塞主线程导致UI挂起.
我尝试并成功地在三台机器上重现了这个问题.每个测试都是使用Visual Studio 2012在64位计算机(Windows 8或Windows 7)上进行的.
我想知道为什么会出现这个问题以及如何解决它(因为没有调试器的运行可能会阻碍开发).
Pan*_*vos 11
问题是你await reader.ReadLineAsync()在一个没有做任何事情的紧密循环中调用- 除了在每次等待之后返回执行到UI线程之后再重新开始.您的UI线程可以在ReadLineAsync()尝试读取行时自由处理Windows事件.
要解决此问题,您可以将呼叫更改为await reader.ReadLineAsync().ConfigureAwait(false).
await等待异步调用的完成并将执行返回到await首先调用的Syncrhonization上下文.在桌面应用程序中,这是UI线程.这是一件好事,因为它允许您直接更新UI,但如果您在之后立即处理异步调用的结果,则可能导致阻塞await.
您可以通过指定ConfigureAwait(false)在不同的线程中继续执行的情况来更改此行为,而不是原始的同步上下文.
即使它不仅仅是一个紧密的循环,你的原始代码也会阻塞,因为处理数据的循环中的任何代码仍将在UI线程中执行.要在不添加的情况下异步处理数据ConfigureAwait,您应该使用eg创建的数据处理数据.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);
}
Run Code Online (Sandbox Code Playgroud)
我在某种程度上看到了和你一样的问题 - 但只是在一定程度上.对我来说,UI在调试器中非常不稳定,偶尔也不会在调试器中生涩.(顺便说一下,我的文件包含许多10个字符的行 - 数据的形状会在这里改变行为.)通常在调试器中开始使用,然后很长时间不好,然后它有时会恢复.
我怀疑问题可能只是你的磁盘太快而你的线路太短.我知道这听起来很疯狂,所以让我解释一下......
当您使用await表达式时,只有在需要时才会通过"附加连续"路径.如果结果已经存在,则代码只提取值并继续在同一个线程中.
这意味着,如果ReadLineAsync始终返回一个在返回时完成的任务,您将有效地看到同步行为.完全有可能ReadLineAsync查看它已经缓冲的数据,并尝试在其中同步查找一行开头.然后,操作系统可能会从磁盘读取更多数据,以便您的应用程序可以使用...这意味着UI线程永远不会有机会抽取其正常消息,因此UI会冻结.
我希望通过网络运行相同的代码将"修复"的问题,但它似乎没有.(请注意,它会改变如何显示急躁情绪.)但是,使用:
await Task.Delay(1);
Run Code Online (Sandbox Code Playgroud)
请问解冻的UI.(Task.Yield但不会,这再次让我感到困惑.我怀疑这可能是延续和其他UI事件之间的优先级问题.)
至于为什么你只是在调试器中看到这个 - 这仍然让我感到困惑.也许这与调试器中如何处理中断有关,可以巧妙地改变时序.
这些只是猜测,但它们至少在某种程度上受过教育.
编辑:好的,我已经找到了一种方法来表明它至少部分与此有关.像这样改变你的方法:
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;
}
Run Code Online (Sandbox Code Playgroud)
...并为UI创建并添加合适的标签.在我的机器上,无论是在调试还是非调试中,我都看到了比"不完整"更"完整"的命中 - 奇怪的是,完整与不完整的比例始终是84:1,都在调试器下,而不是.因此,它只能读到一个在85线的UI后是可以得到一个机会来更新.您应该在您的机器上尝试相同的操作.
作为另一个测试,我在label.Paint事件中添加了一个递增计数器- 在调试器中它只执行了调试器中没有多次的1/10,对于相同数量的行.