I am programming a console based speed reader. I have an automatic mode, where the console prints all the words — one at a time — and shows each word for a period of time set by the user. While printing the individual words I wish to continually check for a key input from the user, without blocking the printing to the console.
My method AutomaticallyReadWordsAsync prints these words asynchronously, but I have found no way to check for a key press asynchronously. The best solution I have come up with is calling the AutomaticallyReadWordsAsync method and not awaiting its results and then calling the IsSpacebarPressed method which leads to first method running asynchronously in the background while the second — synchronous — method continuously checks for user input or cancellation. After the IsSpacebarPressed method I await the task from the AutomaticallyReadWordsAsync. If one the methods completes it cancels the CancellationTokenSource, which leads to the other method completing, as it checks for the state of the token.
Here's a simplified version of my project code to describe this issue:
// See https://aka.ms/new-console-template for more information
var cancellationTokenSource = new CancellationTokenSource();
var automaticallyReadWordsTask = AutomaticallyReadWordsAsync(500, ["Hi", "I", "have", "a", "problem", "please", "help", "me."], cancellationTokenSource);
IsSpacebarPressed(cancellationTokenSource);
await automaticallyReadWordsTask;
async Task AutomaticallyReadWordsAsync(int millisecondsPerWord, string[] words, CancellationTokenSource cancellationTokenSource)
{
CancellationToken cancellationToken = cancellationTokenSource.Token;
int index = 0;
while (index >= 0 && index < words.Length && !cancellationToken.IsCancellationRequested)
{
Console.Clear();
Console.WriteLine(words[index++]);
await Task.Delay(millisecondsPerWord);
}
if (cancellationToken.IsCancellationRequested)
return;
cancellationTokenSource.Cancel();
return;
}
void IsSpacebarPressed(CancellationTokenSource cancellationTokenSource)
{
CancellationToken cancellationToken = cancellationTokenSource.Token;
ConsoleKeyInfo keyPressed;
while (!cancellationToken.IsCancellationRequested)
{
if (Console.KeyAvailable && !cancellationToken.IsCancellationRequested)
{
keyPressed = Console.ReadKey();
if (keyPressed.Key == ConsoleKey.Spacebar)
{
cancellationTokenSource.Cancel();
return;
}
}
}
return;
}
Is there a better way to do this? I feel like there should be one. I don't feel like my solution is bad, but I do feel like there should be a way to do it better.
I could always include useless asynchronous methods in the IsSpacebarPressed method, but that I consider that way worse than the way I do it currently.
yesno matter what the code does - this is unreadable and probably not working.asyncdoesn't mean` two things at the same time` either, that's concurrent. The Console doesn't allow concurrent modifications. When you read or write the Console class itself ensures only one operation will happen at a timeawait Task.Delay(millisecondsPerWord);doesn't listen to the CancellationToken, so this code is no different from checkingReadkeyon every iteration. If you wanted to cancel waiting even duringTask.Delayyou'd have to passcancellationTokentoTask.Delay