Console.ReadLine () 'a Zaman Aşımı Nasıl Eklenir?


122

Kullanıcıya soruyu yanıtlaması için x saniye vermek istediğim bir konsol uygulamam var . Belirli bir süre sonra giriş yapılmazsa, program mantığı devam etmelidir. Zaman aşımının boş yanıt anlamına geldiğini varsayıyoruz.

Buna yaklaşmanın en basit yolu nedir?

Yanıtlar:


112

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:

  • ReadLine dışında bir işlev kullanılır ve işlevsellik kaybına neden olur. (Önceki giriş için Sil / geri al / yukarı tuşu).
  • İşlev, birden çok kez çağrıldığında kötü davranıyor (birden çok iş parçacığı, çok sayıda asılı ReadLine veya başka bir şekilde beklenmeyen davranış).
  • İşlev, meşgul beklemeye dayanır. Beklemenin birkaç saniyeden zaman aşımına kadar her yerde çalışması beklendiğinden bu korkunç bir israftır, bu da birkaç dakika olabilir. Böylesine uzun bir süre boyunca çalışan meşgul bekleme, çok iş parçacıklı bir senaryoda özellikle kötü olan korkunç bir kaynak emmesidir. Meşgul bekleme bir uyku ile değiştirilirse, bunun yanıt verme yeteneği üzerinde olumsuz bir etkisi vardır, ancak bunun muhtemelen çok büyük bir sorun olmadığını kabul ediyorum.

Çö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ı Readernormal Console.ReadLinearamalarla karıştıramazsınız : Readerzaman aşımına uğrarsa, askıda bir ReadLinearama 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ı?

  • Gördüğünüz gibi ReadLine ilk problemden kaçınarak kullanılıyor.
  • İşlev, birden çok kez çağrıldığında düzgün davranır. Bir zaman aşımının gerçekleşip gerçekleşmediğine bakılmaksızın, sadece bir arka plan iş parçacığı çalışacak ve yalnızca bir ReadLine çağrısı aktif olacaktır. Fonksiyonu çağırmak her zaman en son girişle veya bir zaman aşımıyla sonuçlanır ve kullanıcının girişini göndermek için birden fazla kez enter tuşuna basması gerekmez.
  • Ve tabii ki, işlev meşgul beklemeye dayanmıyor. Bunun yerine kaynakların boşa harcanmasını önlemek için uygun çoklu okuma tekniklerini kullanır.

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.ReadLineyine de bir çağrı yapmadan önce eşitleme yapılmalıdır .


1
Bu kodun ardından bir NullReferenceException aldım. Otomatik olayların oluşturulduğu bir kez iş parçacığını başlatmayı düzeltebileceğimi düşünüyorum.
Augusto Pedraza

1
@JSQuareD Uyku (200ms) ile meşgul beklemenin o kadar fazla olduğunu düşünmüyorum horrible waste, ama elbette sinyallemeniz daha üstün. Ayrıca, Console.ReadLineikinci 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
Roland

2
Zamanında giriş yapamazsanız, bu yöntem Console.ReadLine()yaptığınız ilk sonraki aramada bozulur . ReadLineİlk önce tamamlanması gereken bir "hayalet" ile sonuçlanırsınız.
Derek

1
@Derek maalesef bu yöntemi normal ReadLine çağrılarıyla karıştıramazsınız, tüm çağrıların Reader üzerinden yapılması gerekir. Bu sorunun çözümü, zaman aşımı olmadan gotInput bekleyen okuyucuya bir yöntem eklemek olacaktır. Şu anda cep telefonundayım, bu yüzden cevaba kolayca ekleyemiyorum.
JSQuareD

1
İhtiyaç görmüyorum getInput.
silvalli

33
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();

2
Bunun neden oylanmadığını bilmiyorum - kesinlikle kusursuz çalışıyor. Diğer çözümlerin çoğu, düzgün çalışmayan "ReadKey ()" içerir: bu, önceden yazılan komutu almak için "yukarı" tuşuna basmak, geri tuşunu kullanmak gibi ReadLine () 'ın tüm gücünü ok tuşları, vb.
Contango

9
@Gravitas: Bu işe yaramıyor. Bir kez çalışır. Ama ReadLinearadığı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!
Gabe

2
Dikkat. Bu çözüm düzgün görünüyor, ancak askıda kalan 1000'lerce tamamlanmamış çağrıyla sonuçlandım. Bu yüzden tekrar tekrar aranırsa uygun değildir.
Tom Makin

@Gabe, shakinfree: Çözüm için birden fazla çağrı düşünülmedi, ancak zaman aşımına sahip bir eşzamansız çağrı. Sanırım konsolda yazdırılan 10 mesajın olması ve ardından bunlar için tek tek girişleri sırasıyla girmesi kullanıcı için kafa karıştırıcı olacaktır. Asla, asılı aramalar için, TimedoutException satırını yorumlamayı ve boş / boş dizge döndürmeyi deneyebilir misiniz?
gp.

hayır ... sorun Console.ReadLine hala ReadLineDelegate'ten Console.ReadLine yöntemini çalıştıran iş parçacığını engelliyor.
gp.

27

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);
    }
}

Bu doğru, OP bloke edici bir çağrı istiyor gibi görünüyor, ancak bu düşünceye biraz titriyorum ... Bu muhtemelen daha iyi bir çözüm.
GEOCHET

Eminim bunu görmüşsünüzdür. Hızlı bir google social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/…
Gulzar Nazim

Kullanıcı hiçbir şey yapmazsa bunun nasıl "zaman aşımı" olduğunu anlamıyorum. Tüm bunların yapacağı muhtemelen bir tuşa basılıncaya ve diğer mantık devam edene kadar arka planda mantık yürütmeye devam etmektir.
mphair

Doğru, bunun düzeltilmesi gerekiyor. Ancak, zaman aşımını döngü durumuna eklemek yeterince kolaydır.
Jonathan Allen

KeyAvailableyalnı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
Roland

13

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);

4
Zaten yerleşik araçları kullanan en iyi ve en basit çözümün bu olduğunu düşünüyorum. İyi iş!
Vippy

2
Güzel! Basitlik gerçekten en üst düzey karmaşıklıktır. Tebrikler!
BrunoSalvino

10

Ö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:

  • bir ManualResetEvent bildirin, "evt" olarak adlandırın
  • Giriş akışını almak için System.Console.OpenStandardInput çağırın. Verilerini depolayacak ve evt'i ayarlayacak bir geri arama yöntemi belirtin.
  • Eşzamansız bir okuma işlemi başlatmak için bu akışın BeginRead yöntemini çağırın
  • daha sonra bir ManualResetEvent'e zamanlanmış bekleme girin
  • bekleme zaman aşımına uğrarsa okumayı iptal edin

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.


Bu, Kabul Edilen Çözümün yaptığı şeydir.
Roland

9
// 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.
}

8

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.


Evet, anahtarlar için ikinci bir iş parçacığı yoklamanız varsa ve uygulamanız orada beklerken kapanırsa, o anahtar yoklama dizisi sonsuza kadar orada durur.
Kelly Elton

Aslında: ya ikinci bir iş parçacığı ya da "BeginInvoke" ile bir temsilci (perde arkasında bir iş parçacığı kullanır - @gp'den gelen yanıta bakın).
Contango

@ kelton52, Görev Yöneticisi'nde işlemi sonlandırırsanız ikincil iş parçacığı kapanacak mı?
Arlen Beiler

6

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:

  • Silme, geri alma, ok tuşları vb. İçin destek.
  • "Yukarı" tuşuna basma ve son komutu tekrarlama yeteneği (bu, çok kullanım alan bir arka plan hata ayıklama konsolu uygularsanız çok kullanışlıdır).

Benim çözümüm aşağıdaki gibidir:

  1. Bir yumurtlamaya ayrı bir iş parçacığı Console.ReadLine kullanarak kullanıcı girişi işlemek için ().
  2. Zaman aşımı süresinden sonra, http://inputsimulator.codeplex.com/ kullanarak geçerli konsol penceresine bir [enter] tuşu göndererek Console.ReadLine () engelini kaldırın .

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ı?

NET'te başka bir iş parçacığı nasıl iptal edilir, söz konusu iş parçacığı Console.ReadLine'ı çalıştırırken?


5

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.


1
Gönderinizdeki "yukarıda" terimi belirsiz ve kafa karıştırıcı. Başka bir yanıta atıfta bulunuyorsanız, o yanıta uygun bir bağlantı kurmalısınız.
bzlm

5

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;

4

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);
}

2

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();

Konsol görünmüyorsa (?) Veya girdi bir dosyadan yönlendiriliyorsa, Console.In.Peek () öğesini de kullanabileceğinizi unutmayın.
Jamie Kitson

2

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());
      }
   }
}

2
Yalnızca zamanlanmış bir ReadLine () yapmak için yeni bir işlemde ayrı bir yürütülebilir dosya çağırmak çok büyük bir aşırılık gibi geliyor. Esasen ReadLine () 'i iptal edememe sorununu çözüyorsunuz - bunun yerine tüm süreci kurup parçalayarak iş parçacığını bloke ediyorsunuz.
bzlm

O zaman bizi bu duruma getiren Microsoft'a anlatın.
Jesse C. Slicer

Microsoft sizi bu duruma sokmadı. Aynı işi birkaç satırda yapan diğer cevaplardan bazılarına bakın. Yukarıdaki kodun bir çeşit ödül alması gerektiğini düşünüyorum - ancak istediğiniz türden değil :)
Contango

1
Hayır, diğer cevapların hiçbiri OP'nin istediğini tam olarak yapmadı. Hepsi standart girdi rutinleri veya get'in özellikler tüm istekleri gerçeği kafayı kaybetmek 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.
Jesse C. Slicer

1
Hayır, değil. Giriş tamponu hala engelliyor (program engellese bile). Kendiniz deneyin: Birkaç karakter girin ancak enter tuşuna basmayın. Zaman aşımına uğramasına izin verin. Arayandaki istisnayı yakalayın. Ardından, bunu 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. İş.
Jesse C. Slicer

2

.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.


2

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");
    }

1

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.


1

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;
}

1

Bu hoş ve kısa değil mi?

if (SpinWait.SpinUntil(() => Console.KeyAvailable, millisecondsTimeout))
{
    ConsoleKeyInfo keyInfo = Console.ReadKey();

    // Handle keyInfo value here...
}

1
SpinWait de neyin nesi?
john ktejik

1

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!");
        }
    }

1

Kodum tamamen arkadaşımın @JSQuareD cevabına dayanıyor

Ama Stopwatchzamanlayı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.");
            }
        }
    }
}

0

İkinci bir iş parçacığı almanın bir başka ucuz yolu da onu bir temsilciye sarmaktır.


0

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
    }
}

0
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:

  • Silme, geri alma, ok tuşları vb. İçin destek.
  • "Yukarı" tuşuna basma ve son komutu tekrarlama yeteneği (bu, çok kullanım alan bir arka plan hata ayıklama konsolu uygularsanız çok kullanışlıdır).

Bir zaman aşımı eklemek için while döngüsünü uygun şekilde değiştirin.


0

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);
}

0

İş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 .
  • Bu, özel bir tuşa (F1, PrtScn, vb.) Basıldığında girişe '\ 0' karakterlerini enjekte eder. Yine de kodu değiştirerek bunları kolayca filtreleyebilirsiniz.

0

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;
    }
}

0

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);                
        }            
    }

0

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");
}

Ya kullanıcı tuşa basarsa ve 2000ms içinde gitmesine izin verirse?
Izzy

0

Ç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();
}

0

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();
  }    
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.