5

I have an app written in Delphi 2006 that regularly reads from a disk file located elsewhere on a network (100Mb ethernet). Occasionally the read over the network takes a very long time (like 20 secs) and the app freezes, as the read is done from an idle handler in the main thread.

OK, I could put the read operation into it's own thread, but what I would like to know is whether it is possible to specify a timeout for a file operation, so that you can give up and go and do something else, or report the fact that the read has snagged a bit earlier than 20 seconds later.

function ReadWithTimeout (var Buffer     ;
                              N       : integer ; 
                              Timeout : integer) : boolean ;

begin
Result := false
try
    SetReadTimeout (Timeout) ;          //  <==========================???
    FileStream.Read (Buffer, N) ;
    Result := true ;
except 
    ... 
    end ;
end ;
rossmcm
  • 5,493
  • 10
  • 55
  • 118

2 Answers2

9

Open the file for asynchronous access by including the File_Flag_Overlapped flag when you call CreateFile. Pass in a TOverlapped record when you call ReadFile, and if the read doesn't complete immediately, the function will return early. You can control how long you wait for the read to complete by calling WaitForSingleObject on the event you store in the TOverlapped structure. You can even use MsgWaitForMultipleObjects to wait; then you can be notified as soon as the read completes or a message arrives, whichever comes first, so your program doesn't need to hang at all. After you finish processing messages, you can check again whether the I/O is complete with GetOverlappedResult, resume waiting, or give up on the I/O by calling CancelIo. Make sure you read the documentation for all those functions carefully; asynchronous I/O isn't trivial.

Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • I'm sure it would, but I'll leave that to someone more capable (or more motivated) than I, @Workshop. As I said, it's not trivial, so I'm not about to compose an example on the spot. – Rob Kennedy Nov 17 '10 at 15:05
0

After you've moved the read operation to a thread, you could store the value returned by timeGetTime before reading:

isReading := true;
try
  startedAt := timeGetTime;
  FileStream.Read (Buffer, N);
  ...
finally
  isReading := false;
end;

and check in the idle handler if it's taken too long.

eg:

function ticksElapsed( FromTicks, ToTicks : cardinal ) : cardinal;
begin
  if FromTicks < ToTicks
    then Result := ToTicks - FromTicks
    else Result := ( high(cardinal) - FromTicks ) + ToTicks; // There was a wraparound
end;

...

if isReading and ( ticksElapsed( startedAt, timeGetTime ) > 10 * 1000 ) // Taken too long? ~10s
then // Do something
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
TheNewbie
  • 564
  • 2
  • 6
  • 13
  • Problem is: if the read snags in the thread, it will be holding the file open, so even if I detect that it has taken longer than 1 sec say, I wouldn't be able to retry until the thread had reached the finally clause, i.e. until the 20 seconds had elapsed. – rossmcm Nov 17 '10 at 03:09
  • Well, depending on the sharing mode you used to open the file, you should be able to open a second handle to it, and try in parallel with a different thread. You should be careful, though, with issues like overwriting shared data or starting too many threads. – TheNewbie Nov 17 '10 at 03:39