Как сделать ограничение на ввод по времени?

Т.е. если в течении какого то времени не был введен правильный ответ (theard или task?), то переменной основного потока передается какое то значение. Этот код не работает, т.к. string test = await PrintAsync(); блокирует основной поток в ожидании значения

class Program
    {

        async static Task Main(string[] args)
        {

            string test = await PrintAsync();
            async Task <string> PrintAsync()
            {
                await Task.Delay(3000);
                string name = "";
                return name;
            }
            while (true)
            {
                if(test=="")
                {
                    break;
                }
                Console.WriteLine("Ввод ответа");
                string text = Console.ReadLine();
                Console.WriteLine("ответ" + text);

            }

        }
       
    }

Ответы (1 шт):

Автор решения: aepot

Здесь не то что бы борьба с потоками нужна, здесь следует понимать, что консоль не имеет асинхронного API. То есть если вы запросили Console.ReadKey или Console.ReadLine и оно ушло в режим ожидания ввода с консоли, то не сможете это отменить, никак.

Единственный простой лайфхак, как объехать это ограничение, это постоянно оправшивать, есть ли в буфере клавиатуры введенные символы через Console.KeyAvailable.

Так как вы решили делать асинхронно, то есть великолепная вещь - токен отмены CancellationToken, который поможет и с прерыванием ввода, и умеет отменяться сам через какое-то время.

Что ж, теперь зная всё это, осталось написать только свой собственный асинхронный ReadLine, поддерживающий токен отмены.

static async Task<string> ReadLineAsync(CancellationToken token)
{
    var sb = new StringBuilder();
    ConsoleKey key;
    do
    {
        ConsoleKeyInfo keyInfo = await ReadKeyAsync(token, true);
        key = keyInfo.Key;

        if (key == ConsoleKey.Backspace && sb.Length > 0)
        {
            Console.Write("\b \b");
            sb.Length--;
        }
        else if (!char.IsControl(keyInfo.KeyChar))
        {
            Console.Write(keyInfo.KeyChar);
            sb.Append(keyInfo.KeyChar);
        }
    } while (key != ConsoleKey.Enter);
    Console.WriteLine();
    return sb.ToString();
}

static async Task<ConsoleKeyInfo> ReadKeyAsync(CancellationToken token, bool intercept = false)
{
    await WaitForKeyAsync(token);
    return Console.ReadKey(intercept);
}

static async Task WaitForKeyAsync(CancellationToken token)
{
    while (!Console.KeyAvailable)
    {
        await Task.Delay(10, token);
    }
}

Теперь это можно использовать вот так

static async Task Main(string[] args)
{
    const int threshold = 3000;

    Console.Write("Сколько будет 2+2? (3 секунды на ответ): ");
    using var cts = new CancellationTokenSource();
    cts.CancelAfter(threshold);

    try
    {
        string answer = await ReadLineAsync(cts.Token);
        if (answer == "4")
            Console.WriteLine("Правильно");
        else
            Console.WriteLine("Неверно");
    }
    catch (OperationCanceledException) 
    {
        Console.WriteLine();
        Console.WriteLine("Время вышло!");
    }
}

Кстати, чтобы вручную отменить токен, нужно просто вызвать cts.Cancel().


Проверяю

Правильный ввод

Сколько будет 2+2 (3 секунды на ответ): 4
Правильно

Неправильный

Сколько будет 2+2 (3 секунды на ответ): ffff
Неверно

Без ввода или с вводом без нажатия Enter

Сколько будет 2+2 (3 секунды на ответ):
Время вышло!
→ Ссылка