Jestem zaskoczony, gdy dowiaduję się, że po 5 latach we wszystkich odpowiedziach nadal występuje jeden lub więcej z następujących problemów:
- Używana jest funkcja inna niż ReadLine, co powoduje utratę funkcjonalności. (Klawisz Delete / backspace / up-key dla poprzedniego wprowadzenia).
- Funkcja zachowuje się źle, gdy jest wywoływana wiele razy (odradza wiele wątków, wiele zawiesza się ReadLine lub w inny sposób nieoczekiwane zachowanie).
- Funkcja polega na oczekiwaniu zajętości. Co jest okropną stratą, ponieważ oczekuje się, że oczekiwanie będzie trwać od kilku sekund do limitu czasu, który może wynosić wiele minut. Zajęte oczekiwanie, które trwa przez taką ilość czasu, jest okropnym wysysaniem zasobów, co jest szczególnie złe w scenariuszu wielowątkowym. Jeśli modyfikacja zajętości jest uśpiona, ma to negatywny wpływ na responsywność, chociaż przyznaję, że prawdopodobnie nie jest to duży problem.
Wierzę, że moje rozwiązanie rozwiąże pierwotny problem bez żadnego z powyższych problemów:
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.");
}
}
Dzwonienie jest oczywiście bardzo proste:
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.");
}
Alternatywnie możesz skorzystać z TryXX(out)
konwencji, zgodnie z sugestią 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;
}
Który nazywa się w następujący sposób:
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);
W obu przypadkach nie można łączyć połączeń Reader
z normalnymi Console.ReadLine
połączeniami: jeśli Reader
upłynie limit czasu, ReadLine
połączenie zostanie zawieszone . Zamiast tego, jeśli chcesz mieć normalne (nieokreślone w czasie) ReadLine
wywołanie, po prostu użyj Reader
i pomiń limit czasu, aby domyślnie był nieskończony.
A co z tymi problemami innych rozwiązań, o których wspomniałem?
- Jak widać, używany jest ReadLine, unikając pierwszego problemu.
- Funkcja zachowuje się poprawnie, gdy jest wywoływana wiele razy. Niezależnie od tego, czy nastąpi przekroczenie limitu czasu, czy nie, tylko jeden wątek w tle będzie zawsze działał i tylko co najwyżej jedno wywołanie ReadLine będzie kiedykolwiek aktywne. Wywołanie funkcji zawsze spowoduje najnowsze dane wejściowe lub przekroczenie limitu czasu, a użytkownik nie będzie musiał wciskać enter więcej niż raz, aby przesłać swoje dane.
- I, oczywiście, funkcja nie polega na oczekiwaniu zajętości. Zamiast tego wykorzystuje odpowiednie techniki wielowątkowości, aby zapobiec marnowaniu zasobów.
Jedynym problemem, jaki przewiduję w przypadku tego rozwiązania, jest to, że nie jest ono bezpieczne dla wątków. Jednak wiele wątków nie może tak naprawdę prosić użytkownika o wprowadzenie danych w tym samym czasie, więc synchronizacja powinna mieć miejsce przed wykonaniem połączenia Reader.ReadLine
.
horrible waste
, ale oczywiście twoja sygnalizacja jest lepsza. Ponadto użycie jednegoConsole.ReadLine
połączenia blokującego w nieskończonej pętli w drugim zagrożeniu zapobiega problemom z wieloma takimi połączeniami kręcącymi się w tle, jak w przypadku innych, mocno przegłosowanych, rozwiązań poniżej. Dziękujemy za udostępnienie kodu. +1Console.ReadLine()
wywołaniu, które wykonujesz. Otrzymujesz „fantom”,ReadLine
który musi zostać ukończony jako pierwszy.getInput
.źródło
ReadLine
dzwonisz czekając na wejście. Jeśli wywołasz to 100 razy, utworzy 100 wątków, które nie znikną, dopóki nie naciśniesz Enter 100 razy!Czy to podejście przy użyciu pomocy Console.KeyAvailable ?
źródło
KeyAvailable
wskazuje tylko, że użytkownik rozpoczął wpisywanie danych wejściowych do ReadLine, ale potrzebujemy zdarzenia po naciśnięciu klawisza Enter, które powoduje powrót ReadLine. To rozwiązanie działa tylko dla ReadKey, tzn. Uzyskuje tylko jeden znak. Ponieważ nie rozwiązuje to rzeczywistego pytania dotyczącego ReadLine, nie mogę skorzystać z Twojego rozwiązania. -1 przepraszamTo zadziałało dla mnie.
źródło
Tak czy inaczej, potrzebujesz drugiego wątku. Możesz użyć asynchronicznych operacji we / wy, aby uniknąć deklarowania własnego:
Jeśli odczyt zwróci dane, ustaw zdarzenie, a Twój główny wątek będzie kontynuowany, w przeciwnym razie będziesz kontynuował po upływie limitu czasu.
źródło
źródło
Myślę, że będziesz musiał utworzyć dodatkowy wątek i odpytać o klucz na konsoli. Nie znam żadnego sposobu, aby to osiągnąć.
źródło
Walczyłem z tym problemem przez 5 miesięcy, zanim znalazłem rozwiązanie, które doskonale sprawdza się w środowisku biznesowym.
Problem z większością dotychczasowych rozwiązań polega na tym, że opierają się one na czymś innym niż Console.ReadLine () i Console.ReadLine () ma wiele zalet:
Moje rozwiązanie jest następujące:
Przykładowy kod:
Więcej informacji o tej technice, w tym o prawidłowej technice przerywania wątku korzystającego z Console.ReadLine:
Wywołanie .NET w celu wysłania naciśnięcia klawisza [enter] do bieżącego procesu, który jest aplikacją konsoli?
Jak przerwać inny wątek w .NET, gdy ten wątek wykonuje Console.ReadLine?
źródło
Wywołanie Console.ReadLine () w delegacie jest złe, ponieważ jeśli użytkownik nie naciśnie klawisza „enter”, to wywołanie to nigdy nie powróci. Wątek wykonujący delegata zostanie zablokowany, dopóki użytkownik nie naciśnie klawisza „enter”, bez możliwości jego anulowania.
Wysłanie sekwencji takich wywołań nie będzie przebiegać zgodnie z oczekiwaniami. Rozważmy następujące kwestie (używając przykładowej klasy Console z powyższego):
Użytkownik zezwala na upłynięcie limitu czasu dla pierwszego monitu, a następnie wprowadza wartość dla drugiego monitu. Zarówno firstName, jak i lastName będą zawierać wartości domyślne. Kiedy użytkownik naciśnie „enter”, pierwsze wywołanie ReadLine zostanie zakończone, ale kod porzucił to wywołanie i zasadniczo odrzucił wynik. sekund wezwanie ReadLine nadal będzie blokować, timeout ostatecznie wygaśnie i wartość zwracana znowu będzie domyślnym.
BTW - w powyższym kodzie jest błąd. Wywołując waitHandle.Close () zamykasz zdarzenie spod wątku roboczego. Jeśli użytkownik naciśnie „enter” po wygaśnięciu limitu czasu, wątek roboczy spróbuje zasygnalizować zdarzenie, które zgłasza ObjectDisposedException. Wyjątek jest generowany z wątku roboczego, a jeśli nie skonfigurowałeś obsługi nieobsługiwanego wyjątku, proces zostanie zakończony.
źródło
Jeśli jesteś w
Main()
metodzie, nie możesz użyćawait
, więc będziesz musiał użyćTask.WaitAny()
:Jednak C # 7.1 wprowadza możliwość tworzenia
Main()
metody asynchronicznej , więc lepiej jest używaćTask.WhenAny()
wersji zawsze, gdy masz taką opcję:źródło
Być może za dużo czytam w tym pytaniu, ale zakładam, że oczekiwanie byłoby podobne do menu rozruchu, w którym czeka 15 sekund, chyba że naciśniesz klawisz. Możesz użyć (1) funkcji blokującej lub (2) możesz użyć wątku, zdarzenia i licznika czasu. Zdarzenie będzie działać jako „kontynuuj” i będzie blokowane do czasu wygaśnięcia licznika czasu lub naciśnięcia klawisza.
Pseudokod dla (1) to:
źródło
Nie mogę niestety komentować postu Gulzara, ale oto pełniejszy przykład:
źródło
EDYCJA : naprawiono problem, wykonując rzeczywistą pracę w oddzielnym procesie i kończąc ten proces, jeśli upłynie limit czasu. Szczegóły poniżej. Uff!
Po prostu spróbowałem i wydawało się, że działa dobrze. Mój współpracownik miał wersję, która używała obiektu Thread, ale uważam, że metoda BeginInvoke () typów delegatów jest nieco bardziej elegancka.
Projekt ReadLine.exe jest bardzo prostym projektem, który ma jedną klasę, która wygląda następująco:
źródło
Console.ReadLine()
są blokowane i wstrzymują wejście w następnym żądaniu. Przyjęta odpowiedź jest dość bliska, ale nadal ma ograniczenia.ReadLine()
po wywołaniu tego miej w programie inny. Zobacz co się dzieje. Musisz dwukrotnie nacisnąć przycisk powrotu, aby go uruchomić ze względu na jednowątkowy charakter plikuConsole
. To. Nie. Praca.NET 4 sprawia, że jest to niezwykle proste przy użyciu zadań.
Najpierw zbuduj pomocnika:
Po drugie, wykonaj zadanie i poczekaj:
Nie ma potrzeby odtwarzania funkcji ReadLine ani wykonywania innych niebezpiecznych hacków, aby to zadziałało. Zadania pozwalają nam rozwiązać problem w bardzo naturalny sposób.
źródło
Jakby nie było tu wystarczającej ilości odpowiedzi: 0), poniższy fragment zawiera w statycznej metodzie rozwiązanie @ kwl powyżej (pierwsze).
Stosowanie
źródło
Prosty przykład gwintowania, aby rozwiązać ten problem
lub statyczny sznur do góry, aby uzyskać całą linię.
źródło
W moim przypadku to działa dobrze:
źródło
Czy to nie jest ładne i krótkie?
źródło
To jest pełniejszy przykład rozwiązania Glena Slaydena. Zdarzyło mi się to zrobić podczas tworzenia przypadku testowego dla innego problemu. Wykorzystuje asynchroniczne we / wy i zdarzenie resetowania ręcznego.
źródło
Mój kod jest w całości oparty na odpowiedzi znajomego @JSQuareD
Ale musiałem użyć
Stopwatch
timera, ponieważ kiedy skończyłem program zConsole.ReadKey()
nim, wciąż czekałemConsole.ReadLine()
i generował nieoczekiwane zachowanie.U mnie DZIAŁAŁ IDEALNIE. Utrzymuje oryginalną Console.ReadLine ()
źródło
Innym tanim sposobem na uzyskanie drugiego wątku jest zawinięcie go w delegata.
źródło
Przykładowa implementacja powyższego postu Erica. Ten konkretny przykład został użyty do odczytania informacji, które zostały przesłane do aplikacji konsoli za pośrednictwem potoku:
źródło
Zwróć uwagę, że jeśli pójdziesz ścieżką „Console.ReadKey”, stracisz niektóre fajne funkcje ReadLine, a mianowicie:
Aby dodać limit czasu, zmień pętlę while, aby pasowała.
źródło
Proszę, nie nienawidź mnie za dodanie kolejnego rozwiązania do wielu istniejących odpowiedzi! Działa to dla Console.ReadKey (), ale można je łatwo zmodyfikować do pracy z ReadLine () itp.
Ponieważ metody „Console.Read” blokują się, konieczne jest „ szturchnięcie ” ” strumienia stdin do odwołania odczytu.
Składnia wywołania:
Kod:
źródło
Oto rozwiązanie, które wykorzystuje
Console.KeyAvailable
. Są to wywołania blokujące, ale w razie potrzeby wywołanie ich asynchronicznie przez TPL powinno być dość trywialne. Użyłem standardowych mechanizmów anulowania, aby ułatwić połączenie ze wzorcem asynchronicznym zadania i innymi dobrymi rzeczami.Ma to pewne wady.
ReadLine
zapewnia (przewijanie strzałkami w górę / w dół itp.).źródło
Skończyło się tutaj, ponieważ zadano zduplikowane pytanie. Wymyśliłem następujące rozwiązanie, które wygląda na proste. Jestem pewien, że ma kilka wad, które przegapiłem.
źródło
Doszedłem do tej odpowiedzi i skończyłem robiąc:
źródło
Prosty przykład wykorzystujący
Console.KeyAvailable
:źródło
O wiele bardziej współczesny i oparty na zadaniach kod wyglądałby mniej więcej tak:
źródło
Miałem wyjątkową sytuację związaną z posiadaniem aplikacji Windows (usługa Windows). Podczas uruchamiania programu w trybie interaktywnym
Environment.IsInteractive
(VS Debugger lub z cmd.exe) użyłem AttachConsole / AllocConsole, aby uzyskać mój stdin / stdout. Aby proces nie kończył się podczas wykonywania pracy, wywoływany jest wątek interfejsu użytkownikaConsole.ReadKey(false)
. Chciałem anulować oczekiwanie, które wątek UI wykonywał z innego wątku, więc wymyśliłem modyfikację rozwiązania przez @JSquaredD.źródło