c# Console.ReadLine ()에 타임 아웃을 추가하는 방법?





15 Answers

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();
c# .net console timeout io

프롬프트에 응답 할 사용자 x 초를 제공하려는 콘솔 앱이 있습니다. 일정 시간 후에 입력이 없으면 프로그램 로직을 계속 진행해야합니다. 시간 초과는 빈 응답을 의미한다고 가정합니다.

이것에 접근하는 가장 직접적인 방법은 무엇입니까?




한 가지 방법 또는 다른 방법으로 두 번째 스레드가 필요합니다. 비동기 IO를 사용하여 자신의 선언을 피할 수 있습니다.

  • ManualResetEvent를 선언하고 "evt"라고합니다.
  • System.Console.OpenStandardInput을 호출하여 입력 스트림을 가져옵니다. 데이터를 저장하고 evt를 설정할 콜백 메소드를 지정하십시오.
  • 해당 스트림의 BeginRead 메서드를 호출하여 비동기 읽기 작업을 시작합니다.
  • ManualResetEvent에 시간 초과 대기를 입력하십시오.
  • 대기 시간이 초과 된 경우 읽기를 취소합니다.

read가 데이터를 반환하면 이벤트를 설정하면 메인 스레드가 계속 진행되고 그렇지 않으면 시간 초과 후 계속됩니다.




보조 스레드를 만들고 콘솔에서 키를 폴링해야 할 것입니다. 나는이 일을 성취하기 위해 아무 것도 건설되지 않았 음을 안다.




엔터프라이즈 환경에서 완벽하게 작동하는 솔루션을 찾기 전에이 문제로 5 개월 동안 고민했습니다.

지금까지 대부분의 솔루션에 대한 문제점은 Console.ReadLine ()이 아닌 다른 것에 의존하고 있으며 Console.ReadLine ()에 많은 장점이 있다는 것입니다.

  • 삭제, 백 스페이스, 화살표 키 등 지원
  • "up"키를 누르고 마지막 명령을 반복 할 수있는 능력 (많은 용도를 가진 백그라운드 디버깅 콘솔을 구현하면 매우 유용합니다).

내 솔루션은 다음과 같습니다 :

  1. Console.ReadLine ()을 사용하여 사용자 입력을 처리하는 별도의 스레드 를 생성합니다.
  2. 시간 초과 기간이 지나면 http://inputsimulator.codeplex.com/ 사용하여 현재 콘솔 창에 [enter] 키를 보내어 Console.ReadLine ()을 차단 해제합니다.

샘플 코드 :

 InputSimulator.SimulateKeyPress(VirtualKeyCode.RETURN);

Console.ReadLine을 사용하는 스레드를 중단시키는 올바른 기술을 포함하여이 기술에 대한 추가 정보 :

[enter] 키 입력을 현재 프로세스 (콘솔 응용 프로그램)로 보내려면 .NET 호출이 필요합니까?

해당 스레드가 실행 중일 때 .NET에서 다른 스레드를 중단하는 방법 Console.ReadLine?




나는이 질문에 너무 많이 읽었을지도 모르지만, 당신이 키를 누르지 않으면 15 초를 기다리는 부팅 메뉴와 비슷한 기다림이라고 가정하고 있습니다. (1) 차단 기능을 사용하거나 (2) 스레드, 이벤트 및 타이머를 사용할 수 있습니다. 이벤트는 '계속'역할을하며 타이머가 만료되거나 키를 누를 때까지 차단됩니다.

(1)에 대한 의사 코드는 다음과 같습니다.

// 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의 게시물에 불만을 토로 할 수는 없지만 여기에는 완전한 예가 있습니다.

            while (Console.KeyAvailable == false)
            {
                Thread.Sleep(250);
                i++;
                if (i > 3)
                    throw new Exception("Timedout waiting for input.");
            }
            input = Console.ReadLine();



.NET 4는 작업을 사용하여이 작업을 매우 간단하게 만듭니다.

먼저, 도우미를 만드십시오.

   Private Function AskUser() As String
      Console.Write("Answer my question: ")
      Return Console.ReadLine()
   End Function

둘째, 작업으로 실행하고 기다리십시오.

      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

이 작업을 수행하기 위해 ReadLine 기능을 재현하거나 다른 위험한 해킹을 수행하려는 시도는 없습니다. 과제는 우리가 매우 자연스러운 방식으로 질문을 해결하도록합니다.




임명 내 경우이 잘 작동 :

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



마치 여기에 충분한 답변이없는 것처럼 : 0), 다음은 정적 메소드 @ kwl의 솔루션 (위의 첫 번째 메소드)에 캡슐화됩니다.

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

용법

    static void Main()
    {
        Console.WriteLine("howdy");
        string result = ConsoleReadLineWithTimeout(TimeSpan.FromSeconds(8.5));
        Console.WriteLine("bye");
    }



위의 Eric의 게시물 구현 예. 이 특정 예제는 파이프를 통해 콘솔 앱에 전달 된 정보를 읽는 데 사용되었습니다.

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



이거 좋고 짧지 않아?

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

    // Handle keyInfo value here...
}



다음은 Console.KeyAvailable 을 사용하는 솔루션입니다. 이것들은 호출을 막고 있습니다 만, 원한다면 TPL을 통해 비동기 적으로 호출하는 것은 꽤 쉽습니다. 필자는 표준 취소 메커니즘을 사용하여 Task Asynchronous Pattern과 모든 좋은 것들을 쉽게 연결할 수 있도록했습니다.

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

이것에는 몇 가지 단점이 있습니다.

  • ReadLine 제공하는 표준 탐색 기능 (위 / 아래 화살표 스크롤 등)을 얻지 못합니다.
  • 특수 키가 (F1, PrtScn 등)이면 '\ 0'문자를 입력에 삽입합니다. 당신은 쉽게 코드를 수정하여 그들을 필터링 할 수 있습니다.



나는이 대답에 도달하고 결국 :

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



훨씬 더 현대적이고 작업 기반 코드는 다음과 같습니다.

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



다음은 콘솔 입력을 가짜로 설정하여 시간 초과 후 스레드를 차단 해제하는 안전한 솔루션입니다. https://github.com/IgoSol/ConsoleReader 프로젝트는 샘플 사용자 대화 상자 구현을 제공합니다.

var inputLine = ReadLine(5);

public static string ReadLine(uint timeoutSeconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds)
{
    if (timeoutSeconds == 0)
        return null;

    var timeoutMilliseconds = timeoutSeconds * 1000;

    if (samplingFrequencyMilliseconds > timeoutMilliseconds)
        throw new ArgumentException("Sampling frequency must not be greater then timeout!", "samplingFrequencyMilliseconds");

    CancellationTokenSource cts = new CancellationTokenSource();

    Task.Factory
        .StartNew(() => SpinUserDialog(timeoutMilliseconds, countDownMessage, samplingFrequencyMilliseconds, cts.Token), cts.Token)
        .ContinueWith(t => {
            var hWnd = System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle;
            PostMessage(hWnd, 0x100, 0x0D, 9);
        }, TaskContinuationOptions.NotOnCanceled);


    var inputLine = Console.ReadLine();
    cts.Cancel();

    return inputLine;
}


private static void SpinUserDialog(uint countDownMilliseconds, Func<uint, string> countDownMessage, uint samplingFrequencyMilliseconds,
    CancellationToken token)
{
    while (countDownMilliseconds > 0)
    {
        token.ThrowIfCancellationRequested();

        Thread.Sleep((int)samplingFrequencyMilliseconds);

        countDownMilliseconds -= countDownMilliseconds > samplingFrequencyMilliseconds
            ? samplingFrequencyMilliseconds
            : countDownMilliseconds;
    }
}


[DllImport("User32.Dll", EntryPoint = "PostMessageA")]
private static extern bool PostMessage(IntPtr hWnd, uint msg, int wParam, int lParam);



Related