Yanıtlar:
5 yıl sonra, tüm cevapların hala aşağıdaki sorunlardan bir veya birkaçından muzdarip olduğunu öğrendiğimde şaşırdım:
Çözümümün yukarıdaki sorunlardan herhangi biri olmadan orijinal sorunu çözeceğine inanıyorum:
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.");
}
}
Telefon etmek elbette çok kolaydır:
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.");
}
Alternatif olarak, TryXX(out)
shmueli'nin önerdiği gibi kuralı kullanabilirsiniz :
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;
}
Aşağıdaki gibi adlandırılır:
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);
Her iki durumda da aramaları Reader
normal Console.ReadLine
aramalarla karıştıramazsınız : Reader
zaman aşımına uğrarsa, askıda bir ReadLine
arama olur. Bunun yerine, normal (zamanlanmamış) bir ReadLine
çağrı yapmak istiyorsanız Reader
, zaman aşımını kullanın ve atlayın, böylece varsayılan olarak sonsuz bir zaman aşımı olur.
Peki ya bahsettiğim diğer çözümlerin sorunları?
Bu çözümde öngördüğüm tek sorun, iş parçacığı açısından güvenli olmamasıdır. Ancak, birden çok iş parçacığı gerçekten kullanıcıdan aynı anda girdi isteyemez, bu nedenle Reader.ReadLine
yine de bir çağrı yapmadan önce eşitleme yapılmalıdır .
horrible waste
, ama elbette sinyallemeniz daha üstün. Ayrıca, Console.ReadLine
ikinci bir tehditte sonsuz döngüde bir engelleme çağrısı kullanmak , aşağıdaki yoğun şekilde yükseltilen diğer çözümlerde olduğu gibi arka planda asılı kalan bu tür birçok çağrıyla ilgili sorunları önler. Kodunuzu paylaştığınız için teşekkür ederiz. +1
Console.ReadLine()
yaptığınız ilk sonraki aramada bozulur . ReadLine
İlk önce tamamlanması gereken bir "hayalet" ile sonuçlanırsınız.
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
aradığınız her kişi orada oturup girdi bekler. 100 kez çağırırsanız, 100 kez Enter'a basana kadar hepsi kaybolmayan 100 iş parçacığı oluşturur!
Bu yaklaşım Console.KeyAvailable yardımını kullanacak mı?
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
yalnızca kullanıcının ReadLine'a girdi yazmaya başladığını gösterir, ancak Enter tuşuna basıldığında ReadLine'ın geri dönmesini sağlayan bir olaya ihtiyacımız var. Bu çözüm yalnızca ReadKey için çalışır, yani yalnızca bir karakter elde eder. Bu ReadLine için asıl soruyu çözmediğinden, çözümünüzü kullanamıyorum. -1 üzgünüm
Bu benim için çalıştı.
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);
Öyle ya da böyle ikinci bir ipliğe ihtiyacınız var. Kendinizinkini bildirmekten kaçınmak için eşzamansız GÇ'yi kullanabilirsiniz:
Okuma verileri döndürürse, olayı ayarlayın ve ana iş parçacığınız devam edecek, aksi takdirde zaman aşımından sonra devam edeceksiniz.
// 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.
}
Konsolda bir anahtar için ikincil bir iş parçacığı oluşturmanız ve yoklamanız gerekeceğini düşünüyorum. Bunu başarmanın hiçbir yolu olmadığını biliyorum.
Kurumsal bir ortamda mükemmel çalışan bir çözüm bulmadan önce 5 ay boyunca bu sorunla mücadele ettim.
Şimdiye kadarki çözümlerin çoğuyla ilgili sorun, Console.ReadLine () ve Console.ReadLine () dışında bir şeye güvenmeleridir, birçok avantajı vardır:
Benim çözümüm aşağıdaki gibidir:
Basit kod:
InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);
Console.ReadLine kullanan bir iş parçacığını iptal etmek için doğru teknik dahil olmak üzere bu teknik hakkında daha fazla bilgi:
Bir konsol uygulaması olan mevcut işleme [enter] tuş vuruşu göndermek için .NET çağrısı?
Temsilcide Console.ReadLine () 'i çağırmak kötüdür çünkü kullanıcı' enter 'tuşuna basmazsa o çağrı asla geri dönmeyecektir. Temsilciyi yürüten iş parçacığı, kullanıcı iptal etmenin bir yolu olmaksızın 'enter' tuşuna basana kadar engellenecektir.
Bu aramaların bir sırasını yayınlamak, beklediğiniz gibi davranmayacaktır. Aşağıdakileri göz önünde bulundurun (yukarıdaki örnek Console sınıfını kullanarak):
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");
Kullanıcı, ilk komut istemi için zaman aşımının sona ermesine izin verir, ardından ikinci komut istemi için bir değer girer. FirstName ve lastName varsayılan değerleri içerecektir. Kullanıcı 'enter' tuşuna bastığında, ilk ReadLine çağrısı tamamlanır, ancak kod bu çağrıyı bıraktı ve esasen sonucu iptal etti. İkinci ReadLine çağrı engellemeye devam edeceğini, zaman aşımı sonunda sona erecek ve döndürülen değer tekrar varsayılan olacaktır.
BTW- Yukarıdaki kodda bir hata var. WaitHandle.Close () öğesini çağırarak olayı işçi iş parçacığının altından kapatırsınız. Kullanıcı zaman aşımı süresi dolduktan sonra 'enter' tuşuna basarsa, çalışan iş parçacığı bir ObjectDisposedException oluşturan olayı işaret etmeye çalışır. İstisna, çalışan iş parçacığından atılır ve işlenmeyen bir istisna işleyicisi kurmadıysanız, işleminiz sona erer.
Main()
Yöntemin içindeyseniz, kullanamazsınız await
, bu yüzden kullanmanız gerekecek Task.WaitAny()
:
var task = Task.Factory.StartNew(Console.ReadLine);
var result = Task.WaitAny(new Task[] { task }, TimeSpan.FromSeconds(5)) == 0
? task.Result : string.Empty;
Bununla birlikte, C # 7.1, eşzamansız bir Main()
yöntem oluşturma olasılığını sunar , bu nedenle Task.WhenAny()
, bu seçeneğe sahip olduğunuzda sürümü kullanmak daha iyidir :
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;
Soruyu çok fazla okuyabilirim, ancak beklemenin, siz bir tuşa basmadıkça 15 saniye beklediği önyükleme menüsüne benzer olacağını varsayıyorum. Ya (1) bir engelleme işlevi kullanabilir ya da (2) bir iş parçacığı, bir olay ve bir zamanlayıcı kullanabilirsiniz. Olay bir 'devam' olarak hareket edecek ve zamanlayıcının süresi dolana veya bir tuşa basılıncaya kadar bloke olacaktır.
(1) için sözde kod şöyle olacaktır:
// 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'ın gönderisine maalesef yorum yapamam ama işte daha kapsamlı bir örnek:
while (Console.KeyAvailable == false)
{
Thread.Sleep(250);
i++;
if (i > 3)
throw new Exception("Timedout waiting for input.");
}
input = Console.ReadLine();
DÜZENLEME : Asıl işin ayrı bir işlemde yapılmasını sağlayarak ve zaman aşımına uğrarsa bu işlemi sonlandırarak sorunu çözdü. Ayrıntılar için aşağıya bakın. Whew!
Sadece bunu çalıştırdım ve güzel çalışıyor gibiydi. İş arkadaşımın Thread nesnesi kullanan bir sürümü vardı, ancak temsilci türlerinin BeginInvoke () yöntemini biraz daha zarif buluyorum.
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 projesi, böyle görünen tek bir sınıfa sahip çok basit bir projedir:
namespace ReadLine
{
internal static class Program
{
private static void Main()
{
System.Console.WriteLine(System.Console.ReadLine());
}
}
}
Console.ReadLine()
vardır engelleme ve sonraki istek üzerine girişini kadar yapacak. Kabul edilen cevap oldukça yakındır, ancak yine de sınırlamaları vardır.
ReadLine()
aradıktan sonra programınızda bir tane daha bulundurun. Ne olacağını görün. Sen İKİ KEZ dolayı tek iş parçacıklı doğaya gitmek almak için isabet dönüş zorunda Console
. O. Yok mu. İş.
.NET 4, Görevler'i kullanarak bunu inanılmaz derecede basit hale getirir.
İlk önce yardımcınızı oluşturun:
Private Function AskUser() As String
Console.Write("Answer my question: ")
Return Console.ReadLine()
End Function
İkinci olarak, bir görevle yürütün ve bekleyin:
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
Bunu çalıştırmak için ReadLine işlevini yeniden oluşturmaya veya başka tehlikeli hackler yapmaya çalışmak yok. Görevler, soruyu çok doğal bir şekilde çözmemizi sağlar.
Sanki burada yeterince cevap yokmuş gibi: 0), aşağıdaki bir statik yöntem @ kwl'nin yukarıdaki çözümünü (ilki) kapsüller.
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;
}
kullanım
static void Main()
{
Console.WriteLine("howdy");
string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
Console.WriteLine("bye");
}
Bunu çözmek için basit diş açma örneği
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();
}
veya tüm bir satırı almak için yukarıdan statik bir dize.
Benim durumum bu iyi çalışıyor:
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;
}
Bu hoş ve kısa değil mi?
if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
ConsoleKeyInfo keyInfo = Console.ReadKey();
// Handle keyInfo value here...
}
Bu, Glen Slayden'ın çözümünün daha kapsamlı bir örneğidir. Başka bir problem için bir test senaryosu oluştururken bunu yapmayı sevdim. Eşzamansız G / Ç ve manuel sıfırlama olayını kullanır.
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!");
}
}
Kodum tamamen arkadaşımın @JSQuareD cevabına dayanıyor
Ama Stopwatch
zamanlayıcıyı kullanmam gerekiyordu çünkü programı bitirdiğimde Console.ReadKey()
hala bekliyordum Console.ReadLine()
ve beklenmedik davranışlar yarattı.
Benim için MÜKEMMEL ÇALIŞTI. Orijinal Console.ReadLine () 'ı korur
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.");
}
}
}
}
İkinci bir iş parçacığı almanın bir başka ucuz yolu da onu bir temsilciye sarmaktır.
Eric'in yukarıdaki gönderisinin örnek uygulaması. Bu özel örnek, boru aracılığıyla bir konsol uygulamasına iletilen bilgileri okumak için kullanıldı:
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" rotasından aşağı inerseniz ReadLine'ın bazı harika özelliklerini kaybedeceğinizi unutmayın, yani:
Bir zaman aşımı eklemek için while döngüsünü uygun şekilde değiştirin.
Mevcut cevapların bolluğuna başka bir çözüm eklediğim için lütfen benden nefret etmeyin! Bu Console.ReadKey () için çalışır, ancak ReadLine () vb. İle çalışmak üzere kolayca değiştirilebilir.
"Console.Read" yöntemleri engellediğinden, okumayı iptal etmek için StdIn akışını " dürtmek " gerekir.
Çağrı sözdizimi:
ConsoleKeyInfo keyInfo;
bool keyPressed = AsyncConsole.ReadKey(500, out keyInfo);
// where 500 is the timeout
Kod:
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);
}
İşte kullanan bir çözüm Console.KeyAvailable
. Bunlar aramaları engelliyor, ancak istenirse onları TPL aracılığıyla eşzamansız olarak aramak oldukça önemsiz olmalı. Task Asynchronous Pattern ve tüm bu iyi şeyler ile bağlantı kurmayı kolaylaştırmak için standart iptal mekanizmalarını kullandım.
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;
}
}
Bununla ilgili bazı dezavantajlar var.
ReadLine
(Yukarı / aşağı ok kaydırma, vb.) Sağlayan standart gezinme özelliklerini almazsınız .Yinelenen bir soru sorulduğu için burada sona erdi. Basit görünen aşağıdaki çözümü buldum. Kaçırdığım bazı dezavantajları olduğundan eminim.
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;
}
}
Bu cevaba geldim ve sonunda şunu yapıyorum:
/// <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);
}
}
Kullanan basit bir örnek 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");
}
Çok daha çağdaş ve Görev tabanlı kod şuna benzer:
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();
}
Bir Windows Uygulamasına (Windows Hizmeti) sahip olma konusunda benzersiz bir durum yaşadım. Programı etkileşimli olarak çalıştırırken Environment.IsInteractive
(VS Debugger veya cmd.exe'den), stdin / stdout'umu almak için AttachConsole / AllocConsole kullandım. İş yapılırken sürecin bitmesini önlemek için UI İş Parçacığı çağırır Console.ReadKey(false)
. UI iş parçacığının başka bir iş parçacığından yaptığı beklemeyi iptal etmek istedim, bu yüzden çözümde @JSquaredD tarafından bir değişiklik yaptım.
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();
}
}