0

I would like to use this solution to call Console.ReadLine() with a timeout:

delegate string ReadLineDelegate();

string ReadLine(int timeoutms)
{
    string resultstr = null;
    ReadLineDelegate d = Console.ReadLine;
    IAsyncResult result = d.BeginInvoke(null, null);
    result.AsyncWaitHandle.WaitOne(timeoutms);//timeout e.g. 15000 for 15 secs
    if (result.IsCompleted)
    {
        resultstr = d.EndInvoke(result);
        Console.WriteLine("Read: " + resultstr);        
    }
    else
    {
        Console.WriteLine("Timed out!");
        // Bug? resource leak? No d.EndInvoke(), which blocks until Console.ReadLine() returns
    }
    result.AsyncWaitHandle.Close();
    return resultstr;
}

but commenters warned:

every ReadLine you call sits there waiting for input. 
If you call it 100 times, it creates 100 threads 
which don't all go away until you hit Enter 100 times!

...especially because I want to call this repeatedly in a forever-loop.

I understand that every BeginInvoke() needs a EndInvoke() but I don't want a blocking EndInvoke call in the else branch. Somehow we need to abort the running Console.ReadLine() call rather than let it run to completion, because it may never complete.

So all this (complex) code helped me to get Console.ReadLine to return at a timeout, but does not end the Console.ReadLine to quit or otherwise go away.

How can we make this to work correctly, without running into resource leaks?

NB: I added the AsyncWaitHandle.Close() as advised by MS Calling Sync calls asynchronously

Community
  • 1
  • 1
Roland
  • 4,619
  • 7
  • 49
  • 81
  • 1
    Why are you even using that answer? It is a poor answer (despite upvoted heavily). Did you read this [comment](http://stackoverflow.com/questions/57615/how-to-add-a-timeout-to-console-readline#comment10619342_2041489) – Sriram Sakthivel Mar 04 '15 at 14:43
  • even though you'll repeat it, will there be only one console read at a time? – sithereal Mar 04 '15 at 14:49
  • @SriramSakthivel What I like about this solution, despite some comments, is that it pretty much follows the advice of Microsoft on the page I mentioned about the general topic of how to convert any synchronous call to async. Therefore I framed my question differently from the older question – Roland Mar 04 '15 at 14:53
  • I don't think the answer you refer to is a good solution in the case of `ReadLine`. It's basically calling a method asynchronously, and either returns a result in the specified timespan or simple discard the result. This is perfectly fine (but still wasteful) for a *pure* function but most certainly not for a function with side-effects like `ReadLine`. – Dirk Mar 04 '15 at 14:58
  • @Dirk That's what I am wondering. The sync method apparently is NOT killed in any way by the timeout. MS says you always need to call EndInvoke, and this solution does not do that, because that would be waiting, blocking, for completion of the sync call, what we do not want. – Roland Mar 04 '15 at 15:04
  • @Roland Then you'll understand why I think that the resource leak is the least of the problem. What do you suppose happens if you have several of these asynchronous read line calls running and you press enter? – Dirk Mar 04 '15 at 15:06
  • I only want ONE such call running. Either there is input, or the timeout stops readline. Then I do something else, like inspect a flag for stopping the loop, and call readline again, with a timeout. This may iterate hunderds of times each minute, running hours, days, weeks, but all `old` readlines should be gone, and only one left running. But in case of memory leaks, my main process is doomed to crash sometime. – Roland Mar 04 '15 at 15:18
  • Why is `async-await` tag here? – noseratio Mar 05 '15 at 01:22
  • @Noseratio Isn't this a case about `asynchronous programming`? And aren't the `WaitOne` and `AsyncWaitHandle` relevant here? Please correct me if I am wrong. – Roland Mar 05 '15 at 08:33
  • @Roland, you're not using `async` or `await` C# 5.0 concepts here, which is specificaly what the [`async-await` tag](http://stackoverflow.com/tags/async-await/info) is all about. – noseratio Mar 05 '15 at 08:41
  • @Noseratio Thanks for your info. I removed the tag, and will be studying C#5.0 sometime soon. – Roland Mar 05 '15 at 09:34

1 Answers1

0

After reading a lot of comments on several similar questions, as mentioned, I come to believe there is no real solution here. The Microsoft way with Begin/EndInvoke is

  • rather complex, and:
  • not adequate

A more straightforward method is to run the synchronous call in another thread, use a timing method to keep track of the timeout, and use Thread.Abort() to get rid of the timed-out synchronous call.

Caveat:

The synchronous call may or may not support to be aborted. For example, Console.ReadLine() will be aborted OK, but if you restart the thread, no data will be read from the Console anymore.

The accepted solution on the original question on top of my posting above uses a second thread, and a timing method. However, it does not kill the sychronous call but keeps it running because it is needed for subsequent async calls, which is a fine hack.

The code for using a second thread is actually straighforward:

    public class MySyncProc
    {
        /// <summary>
        /// Value returned from the process after completion
        /// </summary>
        public string Result = null;

        ...other shared data...

        public MySyncProc() { }

        public void Run()
        {
            Result = LengthyProcess(...);
            return;
        }
    }

    public string Run(int TimeoutMs)
    {
        MySyncProc SyncP = new MySyncProc() { arg1 = ..., etc };
        //
        Thread T = new Thread(SyncP.Run);
        T.Start();
        //
        DateTime StopTime = DateTime.Now.AddMilliseconds(TimeoutMs);
        while (DateTime.Now < StopTime && SyncP.Result == null)
        {
            Thread.Sleep(200);
        }
        if (T.IsAlive)
        {
            T.Abort();
            Console.WriteLine("Aborted thread for: {0}", Name);
        }
        return SyncP.Result;
    }

If you don't like the polling, use the slightly more complex AutoResetEvent as in the mentioned accepted solution.

Roland
  • 4,619
  • 7
  • 49
  • 81
  • 2
    By no means this can be used in any production code, especially the `Thread.Abort()`. Not only it kills the helper thread in the most disgraceful way, but it also doesn't solve the original problem. Can you be sure about the state integrity of `Console.In` `TextReader` object after you've aborted the thread? Can you safely issue another `Console.ReadLine` call? – noseratio Mar 05 '15 at 01:26
  • @Noseratio Good questions. Could you point me at info about this? My concern was about having lots of `Console.ReadLine` calls running unattended in the background, as a resource leak. If we cannot abort or kill those, what then? I was just so happy to have found `Thread.Abort` as an effective way of killing a synchronous call. – Roland Mar 05 '15 at 08:37
  • 1
    Roland, there's no nice way of doing this with `Console.ReadLine` or even `Console.In.ReadLineAsync`. These APIs don't support cancellation natively. Perhaps, you can resort to the low level `Console.OpenStandardInput`, then use [`Stream.ReadAsync`](https://msdn.microsoft.com/en-us/library/hh193669(v=vs.110).aspx) which accepts a `CancellationToken`. – noseratio Mar 05 '15 at 09:23
  • @Noseratio Correct. I just ran a little test and can confirm that `Console.ReadLine`, once aborted, can be restarted but will not read any data from the Console anymore. I updated my solution with a `caveat`. – Roland Mar 05 '15 at 09:46