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
.