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);
}
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.
When I run the code with the Visual Studio Debugger attached, the code runs synchronously, blocking the main thread causing the UI to hang.
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).
Async/await statements are syntactic sugar created on top of JavaScript Promises. They allow us to write Promise-based code as if it were synchronous, but without blocking the main thread.
Debugging asynchronous code is a challenge because the tasks are often scheduled in one thread and executed in another. Every thread has its own stacktrace, making it difficult to figure out what happened before the thread started.
The best answer to the question "How can I call an async method synchronously" is "don't". There are hacks to try to force it to work, but they all have very subtle pitfalls. Instead, back up and fix the code that makes you "need" to do this.
Async is non-blocking, which means it will send multiple requests to a server. Sync is blocking — it will only send the server one request at a time and will wait for that request to be answered by the server. Async increases throughput because multiple operations can run at the same time.
The problem is that you are calling await reader.ReadLineAsync()
in a tight loop that does nothing - except return execution to the UI thread after each await before starting all over again. Your UI thread is free to process windows events ONLY while ReadLineAsync()
tries to read a line.
To fix this, you can change the call to await reader.ReadLineAsync().ConfigureAwait(false)
.
await
waits for the completion of an asynchronous call and returns execution to the Syncrhonization context that called await
in the first place. In a desktop application, this is the UI thread. This is a good thing because it allows you to update the UI directly but can cause blocking if you process the results of the asynchronous call right after the await
.
You can change this behavior by specifying ConfigureAwait(false)
in which case execution continues in a different thread, not the original Synchronization context.
Your original code would block even if it wasn't just a tight loop, as any code in the loop that processed the data would still execute in the UI thread. To process the data asynchronously without adding ConfigureAwait
, you should process the data in a taks created using eg. Task.Factory.StartNew and await that task.
The following code will not block because processing is done in a different thread, allowing the UI thread to process events:
while ((line= await reader.ReadLineAsync()) != null)
{
await Task.Factory.StartNew(ln =>
{
var lower = (ln as string).ToLowerInvariant();
Console.WriteLine(lower);
},line);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With