From a2aa342d9d1ca84cb252035cfbd0b9703bbf4ce1 Mon Sep 17 00:00:00 2001 From: Olivier Blaise Date: Fri, 11 Dec 2020 07:38:26 +0100 Subject: [PATCH 1/2] Add CancellationToken support --- src/ReadLine/ReadLine.cs | 70 ++++++++++++++++++++++++++++++++++------ 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/ReadLine/ReadLine.cs b/src/ReadLine/ReadLine.cs index 157cf66..81db06f 100755 --- a/src/ReadLine/ReadLine.cs +++ b/src/ReadLine/ReadLine.cs @@ -2,16 +2,29 @@ using Internal.ReadLine.Abstractions; using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; namespace System { public static class ReadLine { private static List _history; - + private static Thread _reader; + private static ManualResetEventSlim _getInput, _gotInput; + private static ConsoleKeyInfo _input; + private static object _lock = new object(); + static ReadLine() { _history = new List(); + _getInput = new ManualResetEventSlim(); + _gotInput = new ManualResetEventSlim(); + _reader = new Thread(ReaderLoop) { + IsBackground = true, + Name = "ReadLine background reader Loop" + }; + _reader.Start(); } public static void AddHistory(params string[] text) => _history.AddRange(text); @@ -19,12 +32,12 @@ static ReadLine() public static void ClearHistory() => _history = new List(); public static bool HistoryEnabled { get; set; } public static IAutoCompleteHandler AutoCompletionHandler { private get; set; } - - public static string Read(string prompt = "", string @default = "") + + public static string Read(string prompt = "", string @default = "", CancellationToken cancellationToken=default) { Console.Write(prompt); KeyHandler keyHandler = new KeyHandler(new Console2(), _history, AutoCompletionHandler); - string text = GetText(keyHandler); + string text = GetText(keyHandler,cancellationToken); if (String.IsNullOrWhiteSpace(text) && !String.IsNullOrWhiteSpace(@default)) { @@ -39,24 +52,63 @@ public static string Read(string prompt = "", string @default = "") return text; } - public static string ReadPassword(string prompt = "") + public static string ReadPassword(string prompt = "",CancellationToken cancellationToken=default) { Console.Write(prompt); KeyHandler keyHandler = new KeyHandler(new Console2() { PasswordMode = true }, null, null); - return GetText(keyHandler); + return GetText(keyHandler,cancellationToken); } - private static string GetText(KeyHandler keyHandler) + private static string GetText(KeyHandler keyHandler,CancellationToken cancellationToken) { - ConsoleKeyInfo keyInfo = Console.ReadKey(true); + ConsoleKeyInfo keyInfo = ReadKeyInternal(cancellationToken); while (keyInfo.Key != ConsoleKey.Enter) { keyHandler.Handle(keyInfo); - keyInfo = Console.ReadKey(true); + keyInfo = ReadKeyInternal(cancellationToken); } Console.WriteLine(); return keyHandler.Text; } + + public static ConsoleKeyInfo ReadKey(bool intercept = false, CancellationToken cancellationToken=default) + { + ConsoleKeyInfo consoleKeyInfo=ReadKeyInternal(cancellationToken); + if (!intercept) + { + if (consoleKeyInfo.Key==ConsoleKey.Enter) + Console.WriteLine(); + else + Console.Write(consoleKeyInfo.KeyChar); + } + return consoleKeyInfo; + } + + public static Task ReadAsync(string prompt,CancellationToken cancellationToken) + { + return Task.Run(()=>ReadLine.Read(prompt,cancellationToken: cancellationToken)); + } + + private static ConsoleKeyInfo ReadKeyInternal(CancellationToken cancellation) + { + if (!_gotInput.IsSet) + { + _getInput.Set(); + _gotInput.Wait(cancellation); + } + _gotInput.Reset(); + return _input; + } + + private static void ReaderLoop() + { + while (true) { + _getInput.Wait(); + _input = Console.ReadKey(true); + _getInput.Reset(); + _gotInput.Set(); + } + } } } From a80f82639c371311af8efac241c2e5c1b37db3bf Mon Sep 17 00:00:00 2001 From: Olivier Blaise Date: Fri, 11 Dec 2020 09:01:07 +0100 Subject: [PATCH 2/2] Extend Demo to show Cancellation --- src/ReadLine.Demo/Program.cs | 55 +++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/ReadLine.Demo/Program.cs b/src/ReadLine.Demo/Program.cs index 29b484e..3e07c30 100755 --- a/src/ReadLine.Demo/Program.cs +++ b/src/ReadLine.Demo/Program.cs @@ -1,5 +1,7 @@ using System; - +using System.Text; +using System.Threading; +using System.Threading.Tasks; namespace ConsoleApplication { public class Program @@ -20,6 +22,57 @@ public static void Main(string[] args) input = ReadLine.ReadPassword("Enter Password> "); Console.WriteLine(input); + + using (CancellationTokenSource cts = new CancellationTokenSource(5000)) + { + + try { + input = ReadLine.Read($"prompt [5 sec]> ",cancellationToken: cts.Token); + Console.WriteLine($"Result: {input}"); + } + catch (OperationCanceledException) + { + Console.WriteLine(" [Operation was aborted]"); + } + } + + int interrupted=0; + Console.Write("Reading several lines using ReadKey [Terminate with CTRL+Q]: "); + StringBuilder sb = new StringBuilder(); + while (true) + using (CancellationTokenSource cts=new CancellationTokenSource(10)) //10 ms to stress the loop + { + try + { + var rki=ReadLine.ReadKey(cancellationToken: cts.Token); + if (rki.KeyChar=='\u0011') // CTRL-Q in unicode + break; + sb.Append(rki.KeyChar); + if (rki.KeyChar=='\r') + sb.Append('\n'); + } + catch (OperationCanceledException) + { + interrupted++; + } + } + Console.WriteLine("ReadKey was interrupted {0} times during read",interrupted); + Console.WriteLine("Entered value:"); + Console.WriteLine("---------------------"); + Console.WriteLine(sb.ToString()); + Console.WriteLine("---------------------"); + + using (CancellationTokenSource cts = new CancellationTokenSource(5000)) + { + try { + input=ReadLine.ReadAsync("Reading line in a Task (5 secs before cancellation)>",cts.Token).GetAwaiter().GetResult(); + Console.WriteLine(input); + } + catch (OperationCanceledException) + { + Console.WriteLine(" [Interupted by timer]"); + } + } } }