14

I have a StreamReader and I want to know if there is data available without blocking the thread.

I tried the Peek method but it blocks when there is no data available.

using (StreamReader reader = new StreamReader(stream))
{
    if (reader.Peek() == -1) // Blocks here while there is no data and unblocks as soon as there is data.
    {

    }
}

If I check the mono code of the Peek() method, it says in comment

    //
    // Peek can block:
    // http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=96484
    //

Unfortunately, the link doesn't work anymore.

I found here, here, here and here that Microsoft seems to have a bug that cause Peek to block. But all these posts are pretty old. I think mono deliberately made Peek() blocking because of this bug.

So I have two questions

  1. Is it still true that Microsoft has a bug causing Peek() to block? If no, mono should change its implementation of Peek() to be non-blocking.
  2. Is there any other way to check if a StreamReader has data available without blocking the thread?
Community
  • 1
  • 1
Alexandre Pepin
  • 1,816
  • 3
  • 20
  • 36
  • You might be able to check if the `BaseStream` is null. – juharr Oct 21 '14 at 20:08
  • @juharr BaseStream is not null. I don't understand what you mean – Alexandre Pepin Oct 21 '14 at 21:00
  • 1
    Please explain yourself better. Why do you care "if data is available"? In pretty much every example I've seen, when someone _thinks_ they want to know if data is available, they don't really. They just think that's the right way to solve some _different_ problem, when it's not. For I/O, it is almost always the case that what you really want to do is to try to read as much data as you can handle, and do so asynchronously so that you don't cause other parts of your program to pause while you do the I/O. You don't need Peek() or anything like it to accomplish that. – Peter Duniho Oct 21 '14 at 21:02
  • I suppose it is indeed not the way to accomplish what I want. – Alexandre Pepin Oct 21 '14 at 23:30
  • @AlexandrePepin: if `BaseStream` is `NetworkStream` then you can use `NetworkStream.DataAvailable`. – drowa Aug 28 '15 at 23:04

3 Answers3

2

OK, let me just say that I don't really know what you are trying to accomplish here. However, from what I can see, the Peek method has to block the current thread in order to work. This is what the documentation says:

The Peek method returns an integer value in order to determine whether the end of the file, or another error has occurred. This allows a user to first check if the returned value is -1 before casting it to a Char type.

So, Peek should only return -1 if it encounters an error or end of the file. This is a little confusing, because there may be no file involved. The stream might be a response from a WebRequest, in which case, the part of the stream that you're trying to read might not be downloaded yet. So, Peek has to wait until it's done because, and this is not clear from the documentation, it returns the first byte read from the stream.

The problems that are mentioned in the links you posted are concerned with multiple threads using the same StreamReader, which is not your case. I also believe that there used to be a bug that would cause one StreamReader waiting for input to block another, but I believe it has since been fixed. I'm not sure what the Mono implementation does.

To answer your question, to do this without blocking the thread, I would try the following:

  1. Just put the whole thing into a separate thread. Then you won't care if it's blocked or not.
  2. Use ReadAsync and then await or ContinueWith on the task to make it non-blocking.

However, as was correctly remarked in the comments, if you put the whole thing into another thread, do you really need Peek? Why not just put it into your typical while (Read(...)) { ... } block and process the data as it's coming?

vesan
  • 3,289
  • 22
  • 35
0
  1. I cant give any answers, I dont have any specific case to reproduce and test, and obviously the source is not available.
  2. With .NET 4.5 you can use the ReadAsync method from the StreamReader class
Louis
  • 593
  • 4
  • 13
  • ReadAsync doesn't block the thread, but doesn't tell me if there is data available. I need to know if the stream has data available or not – Alexandre Pepin Oct 21 '14 at 19:47
  • You can always readAsync a small block and see if the amount of bytes read is greater than 0. This way its used like a peek with the exception that you would consume the first few bytes – Louis Oct 21 '14 at 19:57
  • Actually the source is [available](http://www.dotnetframework.org/default.aspx/DotNET/DotNET/8@0/untmp/whidbey/REDBITS/ndp/clr/src/BCL/System/IO/StreamReader@cs/1/StreamReader@cs) – juharr Oct 21 '14 at 19:59
  • @Louis Even if I ReadAsync one byte and wait for the Task to complete, it will be blocked until that one byte is received – Alexandre Pepin Oct 21 '14 at 20:01
  • 1
    I think what a lot of people seem to be overlooking is that reading no data, and hitting the end of the stream are two different things. A read will not return until the end of stream has been hit. It does not tell you whether data is available or not. It tells you whether the stream is valid or has been hit the end. If for example you are reading from a pipe, or say stdin, or stderror. Your read will not complete until enough bytes have been received on that pipe to satisfy the request. Or if the pipe is closed, and then it will return that the eof was reached. – Dan Nov 20 '14 at 00:00
0

I was trying to read the redirected standard output of a child process and encountered the very same problem. I want to read all characters if there is, otherwise return an empty string without blocking the current thread.

[First attempt]
Usually, we can achieve this by examining the length of the stream. However, in my case, the BaseStream of the StreamReader does not support seeking, which will just throw an exception.

[Second attempt]
I turned to the Peek method. Unfortunately, it is a blocking call, as mentioned in this question.

[Final struggle]
I implement a wrapper for the async I/O methods and finally solved this problem (I hope so).

public static async void appendAll(this StringBuilder sb,
    StreamReader sr, int msTimeout, int bufSize = 4096) {
    for (char[] buf = new char[bufSize]; ;) {
        var n = sr.ReadAsync(buf, 0, buf.Length);
        if (!n.Wait(msTimeout)) { break; }
        sb.Append(new Span<char>(buf, 0, await n));
    }
}

One possible issues is that, if the stream is forcibly closed after timeout, i.e., its corresponding process is killed, exceptions may be thrown inside the async task. I am not sure if it will result in crash.
However, if I understand correctly, an AggregateException will be throw an Wait calls, and it will not bother if we never call Wait on this task again.

Mr. Ree
  • 871
  • 9
  • 20