ฉันมีแอปคอนโซลที่ฉันต้องการให้ผู้ใช้xวินาทีในการตอบสนองต่อการแจ้งเตือน หากไม่มีการป้อนข้อมูลหลังจากช่วงเวลาหนึ่งตรรกะของโปรแกรมควรดำเนินต่อไป เราถือว่าการหมดเวลาหมายถึงการตอบสนองที่ว่างเปล่า
วิธีที่ตรงไปตรงมาที่สุดในการเข้าถึงสิ่งนี้คืออะไร?
ฉันมีแอปคอนโซลที่ฉันต้องการให้ผู้ใช้xวินาทีในการตอบสนองต่อการแจ้งเตือน หากไม่มีการป้อนข้อมูลหลังจากช่วงเวลาหนึ่งตรรกะของโปรแกรมควรดำเนินต่อไป เราถือว่าการหมดเวลาหมายถึงการตอบสนองที่ว่างเปล่า
วิธีที่ตรงไปตรงมาที่สุดในการเข้าถึงสิ่งนี้คืออะไร?
คำตอบ:
ฉันประหลาดใจที่ทราบว่าหลังจากผ่านไป 5 ปีคำตอบทั้งหมดยังคงประสบปัญหาต่อไปนี้อย่างน้อยหนึ่งข้อ:
ฉันเชื่อว่าโซลูชันของฉันจะแก้ปัญหาเดิมได้โดยไม่ต้องทนทุกข์ทรมานจากปัญหาใด ๆ ข้างต้น:
class Reader {
private static Thread inputThread;
private static AutoResetEvent getInput, gotInput;
private static string input;
static Reader() {
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
inputThread = new Thread(reader);
inputThread.IsBackground = true;
inputThread.Start();
}
private static void reader() {
while (true) {
getInput.WaitOne();
input = Console.ReadLine();
gotInput.Set();
}
}
// omit the parameter to read a line without a timeout
public static string ReadLine(int timeOutMillisecs = Timeout.Infinite) {
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
return input;
else
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
แน่นอนว่าการโทรนั้นง่ายมาก:
try {
Console.WriteLine("Please enter your name within the next 5 seconds.");
string name = Reader.ReadLine(5000);
Console.WriteLine("Hello, {0}!", name);
} catch (TimeoutException) {
Console.WriteLine("Sorry, you waited too long.");
}
หรือคุณสามารถใช้การTryXX(out)
ประชุมตามที่ shmueli แนะนำ:
public static bool TryReadLine(out string line, int timeOutMillisecs = Timeout.Infinite) {
getInput.Set();
bool success = gotInput.WaitOne(timeOutMillisecs);
if (success)
line = input;
else
line = null;
return success;
}
ซึ่งมีชื่อเรียกดังนี้
Console.WriteLine("Please enter your name within the next 5 seconds.");
string name;
bool success = Reader.TryReadLine(out name, 5000);
if (!success)
Console.WriteLine("Sorry, you waited too long.");
else
Console.WriteLine("Hello, {0}!", name);
ในทั้งสองกรณีคุณไม่สามารถผสมผสานการโทรเข้าReader
กับการConsole.ReadLine
โทรปกติได้: หากReader
หมดเวลาจะมีReadLine
สายที่ค้างอยู่ แต่หากคุณต้องการให้มีการโทรแบบปกติ (ไม่กำหนดเวลา) ReadLine
ให้ใช้Reader
และละเว้นการหมดเวลาเพื่อให้เป็นค่าเริ่มต้นคือการหมดเวลาที่ไม่มีที่สิ้นสุด
แล้วปัญหาเหล่านั้นของวิธีแก้ปัญหาอื่น ๆ ที่ฉันพูดถึงนั้นเป็นอย่างไร?
ปัญหาเดียวที่ฉันคาดว่าจะใช้วิธีแก้ปัญหานี้คือไม่ปลอดภัยต่อเธรด อย่างไรก็ตามเธรดหลายเธรดไม่สามารถขออินพุตจากผู้ใช้ในเวลาเดียวกันได้ดังนั้นการซิงโครไนซ์จึงควรเกิดขึ้นก่อนที่จะโทรReader.ReadLine
ไป
horrible waste
นั้น แต่แน่นอนว่าการส่งสัญญาณของคุณนั้นเหนือกว่า นอกจากนี้การใช้การConsole.ReadLine
โทรแบบปิดกั้นหนึ่งครั้งในการวนซ้ำแบบไม่สิ้นสุดในภัยคุกคามที่สองจะช่วยป้องกันปัญหาเกี่ยวกับการโทรจำนวนมากที่ห้อยอยู่เบื้องหลังเช่นเดียวกับการแก้ปัญหาด้านล่าง ขอบคุณสำหรับการแบ่งปันรหัสของคุณ +1
Console.ReadLine()
โทรครั้งแรกที่คุณทำ คุณจะจบลงด้วย "ผี" ReadLine
ที่ต้องทำให้เสร็จก่อน
getInput
.
string ReadLine(int timeoutms)
{
ReadLineDelegate d = Console.ReadLine;
IAsyncResult result = d.BeginInvoke(null, null);
result.AsyncWaitHandle.WaitOne(timeoutms);//timeout e.g. 15000 for 15 secs
if (result.IsCompleted)
{
string resultstr = d.EndInvoke(result);
Console.WriteLine("Read: " + resultstr);
return resultstr;
}
else
{
Console.WriteLine("Timed out!");
throw new TimedoutException("Timed Out!");
}
}
delegate string ReadLineDelegate();
ReadLine
คุณโทรมานั่งรอการป้อนข้อมูล หากคุณเรียกมัน 100 ครั้งจะสร้าง 100 เธรดซึ่งจะไม่หายไปทั้งหมดจนกว่าคุณจะกด Enter 100 ครั้ง!
แนวทางนี้จะใช้Console.KeyAvailableช่วยไหม
class Sample
{
public static void Main()
{
ConsoleKeyInfo cki = new ConsoleKeyInfo();
do {
Console.WriteLine("\nPress a key to display; press the 'x' key to quit.");
// Your code could perform some useful task in the following loop. However,
// for the sake of this example we'll merely pause for a quarter second.
while (Console.KeyAvailable == false)
Thread.Sleep(250); // Loop until input is entered.
cki = Console.ReadKey(true);
Console.WriteLine("You pressed the '{0}' key.", cki.Key);
} while(cki.Key != ConsoleKey.X);
}
}
KeyAvailable
ระบุเพียงว่าผู้ใช้เริ่มพิมพ์ input ใน ReadLine แต่เราต้องการเหตุการณ์เมื่อกด Enter ซึ่งจะทำให้ ReadLine กลับมา โซลูชันนี้ใช้ได้กับ ReadKey เท่านั้นกล่าวคือรับอักขระเพียงตัวเดียว เนื่องจากไม่สามารถแก้ปัญหาที่แท้จริงของ ReadLine ได้ฉันจึงใช้วิธีแก้ปัญหาของคุณไม่ได้ -1 ขอโทษ
สิ่งนี้ได้ผลสำหรับฉัน
ConsoleKeyInfo k = new ConsoleKeyInfo();
Console.WriteLine("Press any key in the next 5 seconds.");
for (int cnt = 5; cnt > 0; cnt--)
{
if (Console.KeyAvailable)
{
k = Console.ReadKey();
break;
}
else
{
Console.WriteLine(cnt.ToString());
System.Threading.Thread.Sleep(1000);
}
}
Console.WriteLine("The key pressed was " + k.Key);
ไม่ทางใดก็ทางหนึ่งคุณต้องมีเธรดที่สอง คุณสามารถใช้ IO แบบอะซิงโครนัสเพื่อหลีกเลี่ยงการประกาศของคุณเอง:
หากการอ่านส่งคืนข้อมูลให้ตั้งค่าเหตุการณ์และเธรดหลักของคุณจะดำเนินการต่อมิฉะนั้นคุณจะดำเนินการต่อหลังจากหมดเวลา
// Wait for 'Enter' to be pressed or 5 seconds to elapse
using (Stream s = Console.OpenStandardInput())
{
ManualResetEvent stop_waiting = new ManualResetEvent(false);
s.BeginRead(new Byte[1], 0, 1, ar => stop_waiting.Set(), null);
// ...do anything else, or simply...
stop_waiting.WaitOne(5000);
// If desired, other threads could also set 'stop_waiting'
// Disposing the stream cancels the async read operation. It can be
// re-opened if needed.
}
ฉันคิดว่าคุณจะต้องสร้างเธรดรองและสำรวจความคิดเห็นเกี่ยวกับคีย์บนคอนโซล ฉันรู้ว่าไม่มีทางที่จะทำสิ่งนี้ให้สำเร็จได้
ฉันต่อสู้กับปัญหานี้เป็นเวลา 5 เดือนก่อนที่จะพบวิธีแก้ปัญหาที่ทำงานได้อย่างสมบูรณ์แบบในสภาพแวดล้อมขององค์กร
ปัญหาของวิธีแก้ปัญหาส่วนใหญ่ที่ผ่านมาคือพวกเขาพึ่งพาสิ่งอื่นที่ไม่ใช่ Console.ReadLine () และ Console.ReadLine () มีข้อดีมากมาย:
วิธีแก้ปัญหาของฉันมีดังนี้:
รหัสตัวอย่าง:
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
ข้อมูลเพิ่มเติมเกี่ยวกับเทคนิคนี้รวมถึงเทคนิคที่ถูกต้องในการยกเลิกเธรดที่ใช้ Console.ReadLine:
เรียก. NET เพื่อส่ง [Enter] การกดแป้นพิมพ์ในกระบวนการปัจจุบันซึ่งเป็นแอปคอนโซล
วิธีการยกเลิกเธรดอื่นใน. NET เมื่อเธรดดังกล่าวกำลังดำเนินการ Console.ReadLine?
การเรียก Console.ReadLine () ใน delegate นั้นไม่ดีเพราะหากผู้ใช้ไม่กด 'enter' การโทรนั้นจะไม่กลับมา เธรดที่ดำเนินการกับตัวแทนจะถูกบล็อกจนกว่าผู้ใช้จะกด 'enter' โดยจะไม่มีทางยกเลิกได้
การออกลำดับของการโทรเหล่านี้จะไม่ทำงานตามที่คุณคาดหวัง พิจารณาสิ่งต่อไปนี้ (โดยใช้คลาสคอนโซลตัวอย่างจากด้านบน):
System.Console.WriteLine("Enter your first name [John]:");
string firstName = Console.ReadLine(5, "John");
System.Console.WriteLine("Enter your last name [Doe]:");
string lastName = Console.ReadLine(5, "Doe");
ผู้ใช้ปล่อยให้หมดเวลาสำหรับพรอมต์แรกจากนั้นป้อนค่าสำหรับพรอมต์ที่สอง ทั้ง firstName และ lastName จะมีค่าเริ่มต้น เมื่อผู้ใช้กด 'Enter' การเรียก ReadLine ครั้งแรกจะเสร็จสมบูรณ์ แต่รหัสได้ยกเลิกการเรียกนั้นและทิ้งผลลัพธ์ไป การเรียก ReadLine ครั้งที่สองจะยังคงบล็อกการหมดเวลาจะสิ้นสุดลงในที่สุดและค่าที่ส่งกลับจะเป็นค่าเริ่มต้นอีกครั้ง
BTW- มีข้อผิดพลาดในโค้ดด้านบน โดยการเรียก waitHandle.Close () คุณปิดเหตุการณ์จากใต้เธรดผู้ปฏิบัติงาน หากผู้ใช้กด 'Enter' หลังจากหมดเวลาหมดเวลาเธรดผู้ปฏิบัติงานจะพยายามส่งสัญญาณเหตุการณ์ที่พ่น ObjectDisposedException ข้อยกเว้นจะถูกส่งออกจากเธรดของผู้ปฏิบัติงานและหากคุณไม่ได้ตั้งค่าตัวจัดการข้อยกเว้นที่ไม่สามารถจัดการได้กระบวนการของคุณจะสิ้นสุดลง
หากคุณอยู่ในMain()
วิธีการนี้คุณไม่สามารถใช้ได้await
ดังนั้นคุณจะต้องใช้Task.WaitAny()
:
var task = Task.Factory.StartNew(Console.ReadLine);
var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0
? task.Result : string.Empty;
อย่างไรก็ตาม C # 7.1 แนะนำความเป็นไปได้ในการสร้างMain()
วิธีการasync ดังนั้นจึงควรใช้Task.WhenAny()
เวอร์ชันเมื่อใดก็ตามที่คุณมีตัวเลือกดังกล่าว:
var task = Task.Factory.StartNew(Console.ReadLine);
var completedTask = await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(5)));
var result = object.ReferenceEquals(task, completedTask) ? task.Result : string.Empty;
ฉันอาจจะอ่านคำถามมากเกินไป แต่ฉันคิดว่าการรอจะคล้ายกับเมนูบูตที่รอ 15 วินาทีเว้นแต่คุณจะกดปุ่ม คุณสามารถใช้ (1) ฟังก์ชันการบล็อกหรือ (2) คุณสามารถใช้เธรดเหตุการณ์และตัวจับเวลา กิจกรรมจะทำหน้าที่เป็น "ดำเนินการต่อ" และจะบล็อกจนกว่าตัวจับเวลาจะหมดลงหรือมีการกดปุ่ม
รหัสหลอกสำหรับ (1) จะเป็น:
// Get configurable wait time
TimeSpan waitTime = TimeSpan.FromSeconds(15.0);
int configWaitTimeSec;
if (int.TryParse(ConfigManager.AppSetting["DefaultWaitTime"], out configWaitTimeSec))
waitTime = TimeSpan.FromSeconds(configWaitTimeSec);
bool keyPressed = false;
DateTime expireTime = DateTime.Now + waitTime;
// Timer and key processor
ConsoleKeyInfo cki;
// EDIT: adding a missing ! below
while (!keyPressed && (DateTime.Now < expireTime))
{
if (Console.KeyAvailable)
{
cki = Console.ReadKey(true);
// TODO: Process key
keyPressed = true;
}
Thread.Sleep(10);
}
ฉันไม่สามารถแสดงความคิดเห็นในโพสต์ของ Gulzar ได้ แต่นี่เป็นตัวอย่างที่สมบูรณ์กว่า:
while (Console.KeyAvailable == false)
{
Thread.Sleep(250);
i++;
if (i > 3)
throw new Exception("Timedout waiting for input.");
}
input = Console.ReadLine();
แก้ไข : แก้ไขปัญหาโดยให้งานจริงทำในกระบวนการแยกต่างหากและฆ่ากระบวนการนั้นหากหมดเวลา ดูรายละเอียดด้านล่าง ต๊าย!
เพียงแค่ให้การทำงานนี้และดูเหมือนว่าจะทำงานได้ดี เพื่อนร่วมงานของฉันมีเวอร์ชันที่ใช้อ็อบเจกต์ Thread แต่ฉันพบว่าเมธอด BeginInvoke () ของประเภทผู้ร่วมประชุมดูหรูหรากว่าเล็กน้อย
namespace TimedReadLine
{
public static class Console
{
private delegate string ReadLineInvoker();
public static string ReadLine(int timeout)
{
return ReadLine(timeout, null);
}
public static string ReadLine(int timeout, string @default)
{
using (var process = new System.Diagnostics.Process
{
StartInfo =
{
FileName = "ReadLine.exe",
RedirectStandardOutput = true,
UseShellExecute = false
}
})
{
process.Start();
var rli = new ReadLineInvoker(process.StandardOutput.ReadLine);
var iar = rli.BeginInvoke(null, null);
if (!iar.AsyncWaitHandle.WaitOne(new System.TimeSpan(0, 0, timeout)))
{
process.Kill();
return @default;
}
return rli.EndInvoke(iar);
}
}
}
}
โครงการ ReadLine.exe เป็นโครงการที่ง่ายมากซึ่งมีคลาสเดียวซึ่งมีลักษณะดังนี้:
namespace ReadLine
{
internal static class Program
{
private static void Main()
{
System.Console.WriteLine(System.Console.ReadLine());
}
}
}
Console.ReadLine()
ถูกบล็อกและจะระงับการป้อนข้อมูลในคำขอถัดไป คำตอบที่ยอมรับนั้นค่อนข้างใกล้เคียง แต่ก็ยังมีข้อ จำกัด
ReadLine()
ในโปรแกรมของคุณหลังจากเรียกสิ่งนี้ ดูว่าเกิดอะไรขึ้น คุณต้องกดส่งคืน TWICE เพื่อให้ไปได้เนื่องจากลักษณะเธรดเดียวของไฟล์Console
. มัน. ไม่ งาน.
.NET 4 ทำให้สิ่งนี้ง่ายอย่างเหลือเชื่อโดยใช้ Tasks
ขั้นแรกสร้างผู้ช่วยของคุณ:
Private Function AskUser() As String
Console.Write("Answer my question: ")
Return Console.ReadLine()
End Function
ประการที่สองดำเนินการกับงานและรอ:
Dim askTask As Task(Of String) = New TaskFactory().StartNew(Function() AskUser())
askTask.Wait(TimeSpan.FromSeconds(30))
If Not askTask.IsCompleted Then
Console.WriteLine("User failed to respond.")
Else
Console.WriteLine(String.Format("You responded, '{0}'.", askTask.Result))
End If
ไม่มีการพยายามสร้างฟังก์ชัน ReadLine ขึ้นมาใหม่หรือทำการแฮ็กที่เป็นอันตรายอื่น ๆ เพื่อให้ทำงานได้ Tasks ช่วยให้เราสามารถแก้ปัญหาได้อย่างเป็นธรรมชาติ
ราวกับว่ามีคำตอบไม่เพียงพอที่นี่: 0) ต่อไปนี้จะรวมอยู่ในโซลูชันของวิธีการคงที่ @ kwl ด้านบน (อันแรก)
public static string ConsoleReadLineWithTimeout(TimeSpan timeout)
{
Task<string> task = Task.Factory.StartNew(Console.ReadLine);
string result = Task.WaitAny(new Task[] { task }, timeout) == 0
? task.Result
: string.Empty;
return result;
}
การใช้
static void Main()
{
Console.WriteLine("howdy");
string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
Console.WriteLine("bye");
}
ตัวอย่างเธรดง่ายๆเพื่อแก้ปัญหานี้
Thread readKeyThread = new Thread(ReadKeyMethod);
static ConsoleKeyInfo cki = null;
void Main()
{
readKeyThread.Start();
bool keyEntered = false;
for(int ii = 0; ii < 10; ii++)
{
Thread.Sleep(1000);
if(readKeyThread.ThreadState == ThreadState.Stopped)
keyEntered = true;
}
if(keyEntered)
{ //do your stuff for a key entered
}
}
void ReadKeyMethod()
{
cki = Console.ReadKey();
}
หรือสตริงแบบคงที่ขึ้นด้านบนเพื่อรับทั้งบรรทัด
กรณีของฉันทำงานได้ดี:
public static ManualResetEvent evtToWait = new ManualResetEvent(false);
private static void ReadDataFromConsole( object state )
{
Console.WriteLine("Enter \"x\" to exit or wait for 5 seconds.");
while (Console.ReadKey().KeyChar != 'x')
{
Console.Out.WriteLine("");
Console.Out.WriteLine("Enter again!");
}
evtToWait.Set();
}
static void Main(string[] args)
{
Thread status = new Thread(ReadDataFromConsole);
status.Start();
evtToWait = new ManualResetEvent(false);
evtToWait.WaitOne(5000); // wait for evtToWait.Set() or timeOut
status.Abort(); // exit anyway
return;
}
นี่ไม่ดีและสั้น?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
// Handle keyInfo value here...
}
นี่คือตัวอย่างที่สมบูรณ์ยิ่งขึ้นของการแก้ปัญหาของ Glen Slayden ฉันมีความสุขที่จะทำสิ่งนี้เมื่อสร้างกรณีทดสอบสำหรับปัญหาอื่น ใช้ I / O แบบอะซิงโครนัสและเหตุการณ์รีเซ็ตด้วยตนเอง
public static void Main() {
bool readInProgress = false;
System.IAsyncResult result = null;
var stop_waiting = new System.Threading.ManualResetEvent(false);
byte[] buffer = new byte[256];
var s = System.Console.OpenStandardInput();
while (true) {
if (!readInProgress) {
readInProgress = true;
result = s.BeginRead(buffer, 0, buffer.Length
, ar => stop_waiting.Set(), null);
}
bool signaled = true;
if (!result.IsCompleted) {
stop_waiting.Reset();
signaled = stop_waiting.WaitOne(5000);
}
else {
signaled = true;
}
if (signaled) {
readInProgress = false;
int numBytes = s.EndRead(result);
string text = System.Text.Encoding.UTF8.GetString(buffer
, 0, numBytes);
System.Console.Out.Write(string.Format(
"Thank you for typing: {0}", text));
}
else {
System.Console.Out.WriteLine("oy, type something!");
}
}
รหัสของฉันอ้างอิงจากคำตอบของเพื่อน @JSQuareD ทั้งหมด
แต่ฉันจำเป็นต้องใช้Stopwatch
เพื่อจับเวลาเพราะเมื่อฉันทำโปรแกรมเสร็จโดยที่Console.ReadKey()
มันยังคงรอConsole.ReadLine()
และมันสร้างพฤติกรรมที่ไม่คาดคิด
มันทำงานได้อย่างสมบูรณ์แบบสำหรับฉัน ดูแลคอนโซลเดิม ReadLine ()
class Program
{
static void Main(string[] args)
{
Console.WriteLine("What is the answer? (5 secs.)");
try
{
var answer = ConsoleReadLine.ReadLine(5000);
Console.WriteLine("Answer is: {0}", answer);
}
catch
{
Console.WriteLine("No answer");
}
Console.ReadKey();
}
}
class ConsoleReadLine
{
private static string inputLast;
private static Thread inputThread = new Thread(inputThreadAction) { IsBackground = true };
private static AutoResetEvent inputGet = new AutoResetEvent(false);
private static AutoResetEvent inputGot = new AutoResetEvent(false);
static ConsoleReadLine()
{
inputThread.Start();
}
private static void inputThreadAction()
{
while (true)
{
inputGet.WaitOne();
inputLast = Console.ReadLine();
inputGot.Set();
}
}
// omit the parameter to read a line without a timeout
public static string ReadLine(int timeout = Timeout.Infinite)
{
if (timeout == Timeout.Infinite)
{
return Console.ReadLine();
}
else
{
var stopwatch = new Stopwatch();
stopwatch.Start();
while (stopwatch.ElapsedMilliseconds < timeout && !Console.KeyAvailable) ;
if (Console.KeyAvailable)
{
inputGet.Set();
inputGot.WaitOne();
return inputLast;
}
else
{
throw new TimeoutException("User did not provide input within the timelimit.");
}
}
}
}
อีกวิธีที่ถูกในการรับเธรดที่ 2 คือการรวมไว้ในตัวแทน
ตัวอย่างการใช้งานโพสต์ของ Eric ด้านบน ตัวอย่างเฉพาะนี้ใช้เพื่ออ่านข้อมูลที่ส่งไปยังแอปคอนโซลผ่านไพพ์:
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
namespace PipedInfo
{
class Program
{
static void Main(string[] args)
{
StreamReader buffer = ReadPipedInfo();
Console.WriteLine(buffer.ReadToEnd());
}
#region ReadPipedInfo
public static StreamReader ReadPipedInfo()
{
//call with a default value of 5 milliseconds
return ReadPipedInfo(5);
}
public static StreamReader ReadPipedInfo(int waitTimeInMilliseconds)
{
//allocate the class we're going to callback to
ReadPipedInfoCallback callbackClass = new ReadPipedInfoCallback();
//to indicate read complete or timeout
AutoResetEvent readCompleteEvent = new AutoResetEvent(false);
//open the StdIn so that we can read against it asynchronously
Stream stdIn = Console.OpenStandardInput();
//allocate a one-byte buffer, we're going to read off the stream one byte at a time
byte[] singleByteBuffer = new byte[1];
//allocate a list of an arbitary size to store the read bytes
List<byte> byteStorage = new List<byte>(4096);
IAsyncResult asyncRead = null;
int readLength = 0; //the bytes we have successfully read
do
{
//perform the read and wait until it finishes, unless it's already finished
asyncRead = stdIn.BeginRead(singleByteBuffer, 0, singleByteBuffer.Length, new AsyncCallback(callbackClass.ReadCallback), readCompleteEvent);
if (!asyncRead.CompletedSynchronously)
readCompleteEvent.WaitOne(waitTimeInMilliseconds);
//end the async call, one way or another
//if our read succeeded we store the byte we read
if (asyncRead.IsCompleted)
{
readLength = stdIn.EndRead(asyncRead);
if (readLength > 0)
byteStorage.Add(singleByteBuffer[0]);
}
} while (asyncRead.IsCompleted && readLength > 0);
//we keep reading until we fail or read nothing
//return results, if we read zero bytes the buffer will return empty
return new StreamReader(new MemoryStream(byteStorage.ToArray(), 0, byteStorage.Count));
}
private class ReadPipedInfoCallback
{
public void ReadCallback(IAsyncResult asyncResult)
{
//pull the user-defined variable and strobe the event, the read finished successfully
AutoResetEvent readCompleteEvent = asyncResult.AsyncState as AutoResetEvent;
readCompleteEvent.Set();
}
}
#endregion ReadPipedInfo
}
}
string readline = "?";
ThreadPool.QueueUserWorkItem(
delegate
{
readline = Console.ReadLine();
}
);
do
{
Thread.Sleep(100);
} while (readline == "?");
โปรดทราบว่าหากคุณไปตามเส้นทาง "Console.ReadKey" คุณจะสูญเสียคุณสมบัติเจ๋ง ๆ บางอย่างของ ReadLine ได้แก่ :
หากต้องการเพิ่มระยะหมดเวลาให้ปรับเปลี่ยนลูป while ให้เหมาะสม
โปรดอย่าเกลียดฉันที่เพิ่มวิธีแก้ปัญหาอื่นให้กับคำตอบที่มีอยู่มากมาย! ใช้งานได้กับ Console.ReadKey () แต่สามารถแก้ไขให้ทำงานกับ ReadLine () ฯลฯ ได้อย่างง่ายดาย
เนื่องจากเมธอด "Console.Read" กำลังบล็อกจึงจำเป็นต้อง " เขยิบ " สตรีม StdIn เพื่อยกเลิกการอ่าน
เรียกไวยากรณ์:
ConsoleKeyInfo keyInfo;
bool keyPressed = AsyncConsole.ReadKey(500, out keyInfo);
// where 500 is the timeout
รหัส:
public class AsyncConsole // not thread safe
{
private static readonly Lazy<AsyncConsole> Instance =
new Lazy<AsyncConsole>();
private bool _keyPressed;
private ConsoleKeyInfo _keyInfo;
private bool DoReadKey(
int millisecondsTimeout,
out ConsoleKeyInfo keyInfo)
{
_keyPressed = false;
_keyInfo = new ConsoleKeyInfo();
Thread readKeyThread = new Thread(ReadKeyThread);
readKeyThread.IsBackground = false;
readKeyThread.Start();
Thread.Sleep(millisecondsTimeout);
if (readKeyThread.IsAlive)
{
try
{
IntPtr stdin = GetStdHandle(StdHandle.StdIn);
CloseHandle(stdin);
readKeyThread.Join();
}
catch { }
}
readKeyThread = null;
keyInfo = _keyInfo;
return _keyPressed;
}
private void ReadKeyThread()
{
try
{
_keyInfo = Console.ReadKey();
_keyPressed = true;
}
catch (InvalidOperationException) { }
}
public static bool ReadKey(
int millisecondsTimeout,
out ConsoleKeyInfo keyInfo)
{
return Instance.Value.DoReadKey(millisecondsTimeout, out keyInfo);
}
private enum StdHandle { StdIn = -10, StdOut = -11, StdErr = -12 };
[DllImport("kernel32.dll")]
private static extern IntPtr GetStdHandle(StdHandle std);
[DllImport("kernel32.dll")]
private static extern bool CloseHandle(IntPtr hdl);
}
นี่คือวิธีแก้ปัญหาที่ใช้Console.KeyAvailable
. สิ่งเหล่านี้กำลังปิดกั้นการโทร แต่การโทรแบบอะซิงโครนัสผ่าน TPL ควรเป็นเรื่องเล็กน้อยหากต้องการ ฉันใช้กลไกการยกเลิกมาตรฐานเพื่อให้ง่ายต่อการเชื่อมต่อกับ Task Asynchronous Pattern และสิ่งดีๆทั้งหมดนั้น
public static class ConsoleEx
{
public static string ReadLine(TimeSpan timeout)
{
var cts = new CancellationTokenSource();
return ReadLine(timeout, cts.Token);
}
public static string ReadLine(TimeSpan timeout, CancellationToken cancellation)
{
string line = "";
DateTime latest = DateTime.UtcNow.Add(timeout);
do
{
cancellation.ThrowIfCancellationRequested();
if (Console.KeyAvailable)
{
ConsoleKeyInfo cki = Console.ReadKey();
if (cki.Key == ConsoleKey.Enter)
{
return line;
}
else
{
line += cki.KeyChar;
}
}
Thread.Sleep(1);
}
while (DateTime.UtcNow < latest);
return null;
}
}
มีข้อเสียบางอย่างกับสิ่งนี้
ReadLine
มีให้ (การเลื่อนลูกศรขึ้น / ลง ฯลฯ )จบลงที่นี่เนื่องจากมีการถามคำถามซ้ำ ฉันคิดวิธีแก้ปัญหาต่อไปนี้ซึ่งดูตรงไปตรงมา ฉันแน่ใจว่ามันมีข้อบกพร่องบางอย่างที่ฉันพลาดไป
static void Main(string[] args)
{
Console.WriteLine("Hit q to continue or wait 10 seconds.");
Task task = Task.Factory.StartNew(() => loop());
Console.WriteLine("Started waiting");
task.Wait(10000);
Console.WriteLine("Stopped waiting");
}
static void loop()
{
while (true)
{
if ('q' == Console.ReadKey().KeyChar) break;
}
}
ฉันมาถึงคำตอบนี้และจบลงด้วยการทำ:
/// <summary>
/// Reads Line from console with timeout.
/// </summary>
/// <exception cref="System.TimeoutException">If user does not enter line in the specified time.</exception>
/// <param name="timeout">Time to wait in milliseconds. Negative value will wait forever.</param>
/// <returns></returns>
public static string ReadLine(int timeout = -1)
{
ConsoleKeyInfo cki = new ConsoleKeyInfo();
StringBuilder sb = new StringBuilder();
// if user does not want to spesify a timeout
if (timeout < 0)
return Console.ReadLine();
int counter = 0;
while (true)
{
while (Console.KeyAvailable == false)
{
counter++;
Thread.Sleep(1);
if (counter > timeout)
throw new System.TimeoutException("Line was not entered in timeout specified");
}
cki = Console.ReadKey(false);
if (cki.Key == ConsoleKey.Enter)
{
Console.WriteLine();
return sb.ToString();
}
else
sb.Append(cki.KeyChar);
}
}
ตัวอย่างง่ายๆโดยใช้Console.KeyAvailable
:
Console.WriteLine("Press any key during the next 2 seconds...");
Thread.Sleep(2000);
if (Console.KeyAvailable)
{
Console.WriteLine("Key pressed");
}
else
{
Console.WriteLine("You were too slow");
}
ร่วมสมัยมากขึ้นและโค้ดตามงานจะมีลักษณะดังนี้:
public string ReadLine(int timeOutMillisecs)
{
var inputBuilder = new StringBuilder();
var task = Task.Factory.StartNew(() =>
{
while (true)
{
var consoleKey = Console.ReadKey(true);
if (consoleKey.Key == ConsoleKey.Enter)
{
return inputBuilder.ToString();
}
inputBuilder.Append(consoleKey.KeyChar);
}
});
var success = task.Wait(timeOutMillisecs);
if (!success)
{
throw new TimeoutException("User did not provide input within the timelimit.");
}
return inputBuilder.ToString();
}
ฉันมีสถานการณ์เฉพาะในการมีแอปพลิเคชัน Windows (บริการ Windows) เมื่อรันโปรแกรมแบบโต้ตอบEnvironment.IsInteractive
(VS Debugger หรือจาก cmd.exe) ฉันใช้ AttachConsole / AllocConsole เพื่อรับ stdin / stdout เพื่อไม่ให้กระบวนการสิ้นสุดลงในขณะที่กำลังดำเนินการอยู่ UI Thread จะเรียกConsole.ReadKey(false)
ใช้ ฉันต้องการยกเลิกการรอเธรด UI ที่ทำจากเธรดอื่นดังนั้นฉันจึงได้ทำการแก้ไขโซลูชันโดย @JSquaredD
using System;
using System.Diagnostics;
internal class PressAnyKey
{
private static Thread inputThread;
private static AutoResetEvent getInput;
private static AutoResetEvent gotInput;
private static CancellationTokenSource cancellationtoken;
static PressAnyKey()
{
// Static Constructor called when WaitOne is called (technically Cancel too, but who cares)
getInput = new AutoResetEvent(false);
gotInput = new AutoResetEvent(false);
inputThread = new Thread(ReaderThread);
inputThread.IsBackground = true;
inputThread.Name = "PressAnyKey";
inputThread.Start();
}
private static void ReaderThread()
{
while (true)
{
// ReaderThread waits until PressAnyKey is called
getInput.WaitOne();
// Get here
// Inner loop used when a caller uses PressAnyKey
while (!Console.KeyAvailable && !cancellationtoken.IsCancellationRequested)
{
Thread.Sleep(50);
}
// Release the thread that called PressAnyKey
gotInput.Set();
}
}
/// <summary>
/// Signals the thread that called WaitOne should be allowed to continue
/// </summary>
public static void Cancel()
{
// Trigger the alternate ending condition to the inner loop in ReaderThread
if(cancellationtoken== null) throw new InvalidOperationException("Must call WaitOne before Cancelling");
cancellationtoken.Cancel();
}
/// <summary>
/// Wait until a key is pressed or <see cref="Cancel"/> is called by another thread
/// </summary>
public static void WaitOne()
{
if(cancellationtoken==null || cancellationtoken.IsCancellationRequested) throw new InvalidOperationException("Must cancel a pending wait");
cancellationtoken = new CancellationTokenSource();
// Release the reader thread
getInput.Set();
// Calling thread will wait here indefiniately
// until a key is pressed, or Cancel is called
gotInput.WaitOne();
}
}