4

I have a simple piece of code like so:

File.WriteAllBytes(Path.Combine(temp, node.Name), stuffFile.Read(0, node.FileHeader.FileSize));

One would think that WriteAllBytes would be a blocking call as it has Async counterparts in C# 5.0 and it doesn't state anywhere in any MSDN documentation that it is non-blocking. HOWEVER when a file is of a reasonable size (not massive, but somewhere in the realms of 20mb) the call afterwards which opens the file seems to be called before the writing is finished, and the file is opened (the program complains its corrupted, rightly so) and the WriteAllBytes then complains the file is open in another process. What is going on here?! For curiosity sake, this is the code used to open the file:

System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));

Anyone experience this sort of weirdness before? Or is it me being a blonde and doing something wrong?

If it is indeed blocking, what could possibly be causing this issue?

EDIT: I'll put the full method up.

var node = item.Tag as FileNode;
stuffFile.Position = node.FileOffset;
string temp = Path.GetTempPath();
File.WriteAllBytes(Path.Combine(temp, node.Name), stuffFile.Read(0, node.FileHeader.FileSize));
System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));

What seems to be happening is that Process.Start is being called BEFORE WriteAllBytes is finished, and its attempting to open the file, and then WriteAllBytes complains about another process holding the lock on the file.

jduncanator
  • 2,154
  • 1
  • 22
  • 39
  • How can `WriteAllBytes` complain if something after it is already executing? You should single-step in a debugger to see what's really going on. – Gabe May 25 '13 at 05:07
  • Well thats what I thought. I will update the question with the entire method, maybe that will help. – jduncanator May 25 '13 at 05:11
  • There's no way that `WriteAllBytes` can complain if `Process.Start` is already executing. Even if `WriteAllBytes` were non-blocking (which it isn't), it would still return before executing the next line of code, and once a function has returned it has no ability to complain. – Gabe May 25 '13 at 05:44
  • I have same problem with File.WriteAllBytes. When I write a file with this function, it is telling me, that the process is done. But if another application tries to read this file after my application is done - file seems not to be present. If I debug, there is enough time for file to "appear", so while debugging this workflow works. It also works, if I add a delay of over 2 seconds in between, which is nonsense (and I am using a SSD!!!). My guess is that Windows could have some "optimisation" for "better experience". – subrob sugrobych Jan 21 '20 at 11:55

3 Answers3

5

No, WriteAllBytes is a blocking, synchronous method. As you stated, if it were not, the documentation would say so.

Possibly the virus scanner is still busy scanning the file that you just wrote, and is responsible for locking the file. Try temporarily disabling the scanner to test my hypothesis.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Its not an executable, I'm using Process.Start to invoke the default application set in Windows to open the file. It is definitely not the anti-virus as it is obvious the file isn't finished writing because, for instance, Word complains that the file is corrupt. I doubt the virus scanner could even interrupt a WriteAllBytes call as what it seems to do is Create a file, then write all the bytes and then close the stream. I didn't think AVG would be able to obtain a stream of the file until .NET is finished writing and has closed the stream? – jduncanator May 25 '13 at 05:15
  • 1
    Don't get hung up on my guessing about what the root cause is. You asked whether or not WriteAllBytes is synchronous. The answer is that it is. – David Heffernan May 25 '13 at 05:19
  • 1
    So, do we have to solve the entire puzzle, which relies on information that we don't have? Is it not enough to answer the original question about whether or not WriteAllBytes blocks? If you want people to debug your code, then why hide it? Prepare a short but complete program to demonstrate the issue. So that it can be reproduced. As it stands you asked one simple question which can be answered, but now show signs of morphing this into a chameleon question. – David Heffernan May 25 '13 at 05:23
  • Well, it would be nice to have some hints in the right direction. If you know anything that could be contributing to this issue, that would be nice, otherwise I will mark this question correct and continue on trying to work it out by myself. – jduncanator May 25 '13 at 05:25
  • I gave the most obvious hint. If you want us to help on your specific program, we'd need to have it. Cut down to bare minimum clearly. – David Heffernan May 25 '13 at 05:27
  • I'll upload the whole program. Give me a minute. – jduncanator May 25 '13 at 05:54
  • Interestingly enough, if I "Thread.Sleep" between the two calls, everything is fine! – jduncanator May 25 '13 at 05:56
  • Which will give your scanner time to unlock the file – David Heffernan May 25 '13 at 05:58
  • I disabled my virus scanner. – jduncanator May 25 '13 at 05:59
  • Local drive of course, its dumping them to the "Temp" folder. – jduncanator May 25 '13 at 06:11
  • Bitbukkit repo is here with code in it: https://bitbucket.org/jduncanator/.stuff-file-reader Before all the insults about bad coding style, this is a Proof of Concept ;) – jduncanator May 25 '13 at 06:17
  • There's way too much code there. You need to cut it down to 20 or 30 lines. Read this: http://sscce.org/ I personally won't debug this for you. I answered the original question. – David Heffernan May 25 '13 at 06:32
  • Anyway, even if you cut it down, I wouldn't run it. I'm quite sure it's a local issue on your machine with something scanning new files. – David Heffernan May 25 '13 at 06:39
1

I think your problem may be with the way you are reading from the file. Note that Stream.Read (and FileStream.Read) is not required to read all you request.

In other words, your call stuffFile.Read(0, node.FileHeader.FileSize) might (and definitely will, sometimes) return an array of node.FileHeader.FileSize which contains some bytes of the file at the beginning, and then the 0's after.

The bug is in your UsableFileStream.Read method. You could fix it by having it read the entire file into memory:

    public byte[] Read(int offset, int count)
    {
        // There are still bugs in this method, like assuming that 'count' bytes
        // can actually be read from the file
        byte[] temp = new byte[count];

        int bytesRead;
        while ( count > 0 && (bytesRead = _stream.Read(temp, offset, count)) > 0 )
        {
            offset += bytesRead;
            count -= bytesRead;
        }

        return temp;
    }

But since you are only using this to copy file contents, you could avoid having these potentially massive allocations and use Stream.CopyTo in your tree_MouseDoubleClick:

var node = item.Tag as FileNode;
stuffFile.Position = node.FileOffset;
string temp = Path.GetTempPath();
using (var output = File.Create(Path.Combine(temp, node.Name)))
    stuffFile._stream.CopyTo(output);

System.Diagnostics.Process.Start(Path.Combine(temp, node.Name));
Bojan Resnik
  • 7,320
  • 28
  • 29
  • Thanks, I'll take a look. The reason why the entire file isn't loaded into memory is because they are sometimes 300mb or bigger. The file is indexed (eg. every file inside the file has its info stored before the actual data so you can easily "read" the part of the file you need whenever you need it without loading the entire file into memory). The specs for the format say that there will ALWAYS be the correct bytes there, even if they are padded. I will check tho :) Thanks for your answer. – jduncanator May 26 '13 at 08:02
0

A little late, but adding for the benefit of anyone else that might come along.

The underlying C# implementation of File.WriteAllBytes may well be synchronous, but the authors of C# cannot control at the OS level how the writing to disk is handled.

Something called write caching means that when C# asks to save the file to disk, the OS may return "I'm done" before the file is fully written to the disk, causing the issue OP highlighted.

In that case, after writing, it may be better to sleep in a loop and keep checking to see if the file is still locked before calling Process.Start.

You can see that I run into problems caused by this here: C#, Entity Framework Core & PostgreSql : inserting a single row takes 20+ seconds

Also, in the final sentence of OPs post "and then WriteAllBytes complains about another process holding the lock on the file." I think they actually meant to write "and then Process.Start complains" which seems to have caused some confusion in the comments.

F Chopin
  • 574
  • 7
  • 23