The following code never returns for me and I don't understand why. It gets stuck on var s = await
and never continues. What exactly am I doing wrong? I am running this from VS2022 and it is compiled for .NET 6.0.
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(2));
cts.Token.Register(() => Console.WriteLine("cancelled"));
Console.WriteLine(DateTime.Now);
var s = await Console.In.ReadLineAsync().WaitAsync(cts.Token);
Console.WriteLine(DateTime.Now);
Environment.Exit(0);
Reading from the console with a timeout is surprisingly hard and async
in Console.In is all pretend :)
You may notice that Console.In
on Windows is System.IO.SyncTextReader
and
ReadLineAsync
, and even .NET7's ReadLineAsync(CancellationToken cancellationToken)
, are not that cancellable (from source):
public override Task<string?> ReadLineAsync()
{
return Task.FromResult(ReadLine());
}
public override ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
{
return cancellationToken.IsCancellationRequested ?
ValueTask.FromCanceled<string?>(cancellationToken) :
new ValueTask<string?>(ReadLine());
}
I see that there are solid answers for the questions of Why is this .NET 6.0 async code not returning and what am I doing wrong (nothing). That being the case, I had to wonder what it would take to fulfill on the original outcome. In the process of reproducing the original issue I came up with a method that conceivably might be useful even though it doesn't directly answer the question as asked.
I would also point out that even if the original code had succeeded, the user only has 2 seconds to complete the entire line. There's no recognition of typing effort and if they miss the deadline to ENTER by even 1 tick then it's all gone. So this method offers the added benefit of restarting the timeout interval every time a key is pressed.
async Task<string> ReadLineWithTimeout(TimeSpan timeout)
{
List<char> currentLine = new List<char>();
var wdtStartOrRestart = DateTime.Now;
while (true)
{
while (Console.KeyAvailable)
{
wdtStartOrRestart = DateTime.Now;
ConsoleKeyInfo keyInfo = Console.ReadKey(intercept: true);
switch (keyInfo.Key)
{
case ConsoleKey.Enter:
Console.WriteLine();
goto breakFromSwitch;
case ConsoleKey.Backspace:
Console.Write("\b \b");
var removeIndex = currentLine.Count - 1;
if(removeIndex != -1)
{
currentLine.RemoveAt(removeIndex);
}
break;
case ConsoleKey.LeftArrow:
case ConsoleKey.RightArrow:
// Handling these is more than we're taking on right now.
break;
default:
Console.Write(keyInfo.KeyChar);
currentLine.Add(keyInfo.KeyChar);
break;
}
}
await Task.Delay(1);
if(DateTime.Now.Subtract(wdtStartOrRestart) > timeout)
{
throw new TaskCanceledException();
}
}
breakFromSwitch:
return string.Join(String.Empty, currentLine);
}
Net Core 6 Test
var timeout = TimeSpan.FromSeconds(5);
while (true)
{
Console.WriteLine();
Console.WriteLine(DateTime.Now);
try
{
Console.WriteLine(await ReadLineWithTimeout(timeout) + "(ECHO)");
}
catch
{
Console.WriteLine("Cancelled");
}
Console.WriteLine(DateTime.Now);
}
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