Reputation: 650
Is using the FileStream class to write to a file and the .NET File.Copy method to copy the file at the same time thread safe? It seems like the operating system should safely handle concurrent access to the file, but I cannot find any documentation on this. I've written a simple application to test and am seeing weird results. The copy of the file is showing to be 2MB, but when I inspect the file content with notepad++ it's empty inside. The original file contains data.
using System;
using System.Threading.Tasks;
using System.Threading;
using System.IO;
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
string filePath = Environment.CurrentDirectory + @"\test.txt";
using (FileStream fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
Task fileWriteTask = Task.Run(() =>
{
for (int i = 0; i < 10000000; i++)
{
fileStream.WriteByte((Byte)i);
}
});
Thread.Sleep(50);
File.Copy(filePath, filePath + ".copy", true);
fileWriteTask.Wait();
}
}
}
}
Thanks for the help!
Upvotes: 2
Views: 2266
Reputation: 10868
The answer is no. You cannot in general operate on file system objects from different threads and achieve consistent or predictable results for the file contents.
Individual .NET Framework functions may or may not be thead-safe, but this is of little consequence. The timing and order in which data is read from, written to or copied between individual files on disk is essentially non-deterministic. By which I mean that if you do the same thing multiple times you will get different results, depending on factors outside your control such as machine load and disk layout.
The situation is made worse because the Windows API responsible for File.Copy is run on a system process and is only loosely synchronised with your program.
Bottom line is that if you want file level synchronisation you have no choice but to use file-level primitives to achieve it. That means things like open/close, flushing, locking. Finding combinations that work is non-trivial.
In general you are better off keeping all the operations on a file inside one thread, and synchronising access to that thread.
In answer to a comment, if you operate on a file by making it memory-mapped, the in-memory contents are not guaranteed to be consistent with the on-disk contents until the file is closed. The in-memory contents can be synchronised between processes or threads, but the on-disk contents cannot.
A named mutex locks as between processes, but does not guarantee anything as to consistency of file system objects.
File system locks are one of the ways I mentioned that could be used to ensure file system consistency, but in many situations there are still no guarantees. You are relying on the operating system to invalidate cached disk contents and flush to disk, and this is not guaranteed for all files at all times. For example, it may be necessary to use the FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED and FILE_FLAG_WRITE_THROUGH flags, which may severely affect performance.
Anyone who thinks this is an easy problem with a simple one-size-fits-all solution has simply never tried to get it to work in practice.
Upvotes: 0
Reputation: 8099
It depends.
Depends what do you mean when you say "thread safe".
First of all, look at this constructor:
public FileStream(string path, FileMode mode, FileAccess access, FileShare share )
notice the last parameter, it states what do you allow other threads and processes to do with the file. the default that applies to constructors that don't have it is FileShare.Read
, which means you allow others to view the file as read-only. this is of course unwise if you are writing to it.
That's what you basically did, you opened a file for writing, while allowing others to read it , and "read" includes copying.
Also please note, that without this: fileWriteTask.Wait();
at the end of your code, your entire function isn't thread safe, because the FileStream might be closed before you even start writing.
Windows does make file access thread safe, but in a pretty non trivial manner. for example if you would have opened the file with FileShare.None
, it would have crashed File.Copy
and to the best of my knowledge there isn't an elegant way to do this with .Net. The general approach Windows uses to synchronize file access is called optimistic concurrency, meaning to assume your action is possible, and fail if it isn't.
this question discusses waiting for file lock in .Net
Sharing files between process is a common issue and one of the ways to do this , mostly for Inter-Process Comunication is memory mapped files and this is the MSDN documentation
If you are brave and willing to play around with WinAPI and Overlapped IO, If I remember correctly LockFileEx allows nice file locking...
Also, once there was a magical thing called Transactional NTFS but it has moved on in to the realm of Microsoft Deprecated Technologies
Upvotes: 2
Reputation: 100527
It is thread-safe in a sense of "neither of C# object would be corrupted".
Result of operation will be more or less random (empty file, partial copy, access denied) and depends on sharing mode used to open file for each operation.
If carefully setup this can produce sensible results. I.e. flush file after each line and specify compatible share mode will allow to be reasonably sure that complete lines are copied.
Upvotes: 2