Jump to content
dummzeuch

while TStream_TryRead() do

Recommended Posts

It has always irked me that in C you can write:

while (0<(BytesRead=Read(...))) {
  // ..
}

 

But in Delphi you have to use the much less readable

 

BytesRead := Stream.Read(Buffer, BufSize);
while BytesRead > 0 do begin
  // ..
  BytesRead := Stream.Read(Buffer, BufSize);
end;

Today I had enough and wrote these simple helper functions:

function TStream_TryRead(_st: TStream; var _Buffer: TBytes; _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline;
begin
  _BytesRead := _st.Read(_Buffer, _Count);
  Result := (_BytesRead > 0);
end;

function TStream_TryRead(_st: TStream; var _Buffer; _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline;
begin
  _BytesRead := _st.Read(_Buffer, _Count);
  Result := (_BytesRead > 0);
end;

function TStream_TryRead(_st: TStream; _Buffer: TBytes; _Offset, _Count: Int32; out _BytesRead: Int32): Boolean; overload; inline;
begin
  _BytesRead := _st.Read(_Buffer, _Offset, _Count);
  Result := (_BytesRead > 0);
end;

function TStream_TryRead(_st: TStream; _Buffer: TBytes; _Offset, _Count: Int64; out _BytesRead: Int64): Boolean; overload; inline;
begin
  _BytesRead := _st.Read64(_Buffer, _Offset, _Count);
  Result := (_BytesRead > 0);
end;

 

With these you can write:

while TStream_TryRead(Buffer, BufSize, BytesRead) do begin
    // ..
end;

Yes, that's far from being rocket science. It's probably possible to convert these into a class helper, but I can't be bothered.

 

(Original Blog post.)

  • Like 1

Share this post


Link to post
13 hours ago, uligerhardt said:

Wouldn't a repeat-until loop do the trick?

Care to provide an example?

Share this post


Link to post
9 hours ago, dummzeuch said:

Care to provide an example?

repeat
  BytesRead := Stream.Read(Buffer, BufSize);
  if BytesRead <= 0 then Break;
  // ..
until False;

 

Share this post


Link to post
13 hours ago, Remy Lebeau said:

repeat
  BytesRead := Stream.Read(Buffer, BufSize);
  if BytesRead <= 0 then Break;
  // ..
until False;

 

Hm, an endless loop with an obscured break is not quite what I would call elegant and easily readable. But yes, that's one way a repeat loop "would do the trick".

Edited by dummzeuch

Share this post


Link to post

Weird repeat loop. I'd do:

 

repeat

  BytesRead := Stream.Read(Buffer, BufSize);

until BytesRead < BufSize;

  • Thanks 1

Share this post


Link to post

@David Heffernan, that was my first idea, too. There is just some code missing that handles the BytesRead > 0 case before the loop exits. Some if-then after the read cannot be avoided in this case.

Share this post


Link to post

I think a repeat is conceptually the right thing here, as you want to read at least once unconditionally.

And of course written like David did. 😉

Share this post


Link to post

I not advocating the repeat. I think the TryRead is nice. Class helper for TStream would be good. I general TryXXX methods tend to be useful in loads of places. 

Share this post


Link to post
1 hour ago, David Heffernan said:

Weird repeat loop. I'd do:

 

repeat

  BytesRead := Stream.Read(Buffer, BufSize);

until BytesRead < BufSize;

Read() is not guaranteed to return as many bytes as requested. The documentation even says so. Use other ReadXXX() methods for that purpose instead. Since Read() can return fewer bytes without actually failing, breaking the loop on a lesser return is wrong if the intent is to read the whole stream. Only a return of -1 (error) or 0 (end of stream) should be the actual breaking condition.

 

I prefer using 'repeat ... until False' because it avoids having to test the return value twice, eg:

 

repeat
  BytesRead := Stream.Read(Buffer, BufSize);
  if BytesRead > 0 then
    ... 
until BytesRead <= 0;

 

Edited by Remy Lebeau

Share this post


Link to post
49 minutes ago, Remy Lebeau said:

Only a return of -1 (error)

I didn't see that condition documented. I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left. 

 

That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested. 

Share this post


Link to post
2 hours ago, David Heffernan said:

I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left. 

 

That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested. 

That depends on what you want to do with these bytes. It might be crucial to process them as fast as they can be read.

E.g. if they come from a device (e.g. via serial port) and you need a time stamp to know when exactly they were received (Yes, I know from experience that this would be far from reliable. We are talking Windows, not some real time OS here.)

Edited by dummzeuch

Share this post


Link to post
4 hours ago, David Heffernan said:

Weird repeat loop. I'd do:

 

repeat

  BytesRead := Stream.Read(Buffer, BufSize);

until BytesRead < BufSize;

Shouldn't that be until BytesRead <= 0 ?

Hm, And where does the code to process those bytes go in this case?

I admit I should have included a call for that in my original example. Something like this:

while TStream_TryRead(Buffer, BufSize, BytesRead) do begin
    Process(Buffer, BytesRead)
end;

Because what's the point in reading from the stream when you just throw away the data read from it? Unless of course you want to simply empty the stream. Of course, the Process procedure itself could check whether the BytesRead parameter is <= 0. In that case a repeat loop would work:

repeat
  BytesRead := Stream.Read(Buffer, BufSize);
  Process(Buffer, BytesRead;
until BytesRead <= 0;

But that just means that the if statement goes into the Process procedure instead of the loop:

repeat
  BytesRead := Stream.Read(Buffer, BufSize);
  if BytesRead > 0 then
    Process(Buffer, BytesRead;
until BytesRead <= 0;

 

Edited by dummzeuch

Share this post


Link to post

 

2 hours ago, David Heffernan said:

That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all requested.

This design was made to handle streaming sources (socket, pipe, file being written, etc). At the moment of call there could be no data but next time it could arrive (I'm sure you know it anyway). But it really could puzzle those who are not aware of this behavior and are sure that Read() really waits until it reads requested amount of data.

  • Like 1

Share this post


Link to post
5 hours ago, David Heffernan said:

I didn't see that condition documented. I also hadn't realised that some streams can read fewer than the requested bytes but there still could be more left.

Sadly, not only is -1 not documented, but it's also not always implemented. For example, THandleStream.Read() returns 0 on failure and end-of-file. Which makes error handling practically impossible for some stream types.

5 hours ago, David Heffernan said:

That does seem a strange design choice. If you can wait for at least one byte then surely you can wait for all.

That is what the ReadXXX() methods are meant for.  Read() is the lower level method that they all use internally.

  • Like 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×