Does FileShare.None make threads wait until the filestream is closed? - c#

When using a file stream, and setting FileShare to None, and say two users accessing the same function at the same time want to read/write to that file. Will FileShare.None make the second users request waiting or will the second user's request throw an exception?
//two users get to this this code at the same time
using (FileStream filestream = new FileStream(chosenFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
using (StreamReader sr = new StreamReader(filestream))
using (StreamWriter sw = new StreamWriter(filestream))
{
//reading and writing to file
}
Msdn says: None Declines sharing of the current file. Any request to open the file (by this process or another process) will fail until the file is closed.
But will requests keep trying until the filestream is closed?

When a process opean a file for Read/Write with FileShare.None any subsequent access by any process on this same file will result in Acess Denied Exception. To answer your question, Second user will get exception.
MSDN: FileShare.None - Declines sharing of the current file. Any request to open the
file (by this process or another process) will fail until the file is
closed.
There are many ways you can handle these kind of concurrent file access issues, Following code demonstrates a simple approach to tackle this situation.
//Retry 5 times when file access fails
int retryCounter = 5;
while (!isFileAccessSuccess && retryCounter > 0)
{
try
{
//Put file access logic here
//If the file has been accessed successfully set the flag to true
isFileAccessSuccess = true;
}
catch (Exception exception)
{
//Log exception
}
finally
{
//Decrease the retry count
--retryCounter;
}
if (!isFileAccessSuccess)
{
//Wait sometime until initiating next try
Thread.Sleep(10000);
}
}

No, IOException will be thrown an with HResult = -2147024864 and Message = The process cannot access the file 'path' because it is being used by another process.
if you want to synchronize access to a file you can use a named wait handle.
public class FileAcessSynchronizer
{
private readonly string _path;
private readonly EventWaitHandle _waitHandle;
public FileAcessSynch(string path)
{
_path = path;
_waitHandle = new EventWaitHandle(true, EventResetMode.AutoReset, "NameOfTheWaitHandle");
}
public void DoSomething()
{
try
{
_waitHandle.WaitOne();
using (FileStream filestream = new FileStream(chosenFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
using (StreamReader sr = new StreamReader(filestream))
using (StreamWriter sw = new StreamWriter(filestream))
{
//reading and writing to file
}
}
finally
{
_waitHandle.Set();
}
}
}
since named wait handle creates a critical section no two threads or processes of your application (that use same name as wait handle name) can execute the codes in it concurrently. So one thread or process enters the section, opens the file in the way that no one can access it (other applications), execute commands and at the end leaves the critical section to allow other threads or processes of your application enters the critical section.

Related

Read/Write lock in c#

Our application needs to lock down a file to read-only when it is opened the first time. I have the code below, which doesn't seem to work anymore.
Now, for some reason, it never triggers the exception when I open a second file and just passes through the first statement every time.
From what I understand, the logic behind this code is this: The first file opens the file with Read/Write (FileAccess.ReadWrite) and sets the file to Read for subsequent users (FileShare.Read).
When it is opened the second time, it will try and open it with Read/Write but is restricted to Read-Only upon which it triggers the exception and goes to the second statement. There it will open the file as Read-Only(FileAccess.Read) and set it back to read-write(FileAccess.ReadWrite) for subsequent users, this to ensure that the first document does not get locked out of its already permitted Write rights, which caused an exception.
I tested this already, it used to work with below code. I can still verify that the read-lock is set. When I open the file in another PDF editor, it cannot save, just read. When I do it in my own application, I can save whenever I want.
Am I missing something, or was there a fluke so that it temporarily worked ?
try
{
m_FileStream = File.Open(fileName, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
m_bCanWrite = true;
}
catch (Exception)
{
m_FileStream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
m_bCanWrite = false;
}
I changed the code so it uses using blocks, with the same params, but the result is also the same.
try
{
FileInfo info = new FileInfo(fileName);
using (FileStream readWriteStream = info.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
m_bcanWrite = true;
m_PdfDocument = new PDFDoc(readWriteStream);
}
}
catch (Exception)
{
FileInfo info = new FileInfo(fileName);
using (FileStream readStream = info.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
m_bcanWrite = false;
m_PdfDocument = new PDFDoc(readStream);
}
}
As we figured out in comments - the reason was premature closing of the file stream. Of course when you close filestream - all locks on it are released. It's not a good idea to hold the file open for the duration of the whole application run time as you suggested in comment - just close the file when you are done with it, not sooner but not later.

Using StreamReader / StreamWriter to grab logs causes program to cease responding

I'm attempting to use StreamReader and StreamWriter to grab a temporary output log (.txt format) from another application.
The output log is always open and constantly written to.
Unhelpfully if the application closes or crashes, the log file ends up deleted - hence the need for a tool that can grab the information from this log and save it.
What my program currently does is:
Create a new .txt file, and stores the path of that file as the
string "destinationFile".
Finds the .txt log file to read, and stores the path of that file as
the string "sourceFile"
It then passes those two strings to the method below.
Essentially I'm trying to read the sourceFile one line at a time.
Each time one line is read, it is appended to destinationFile.
This keeps looping until the sourceFile no longer exists (i.e. the application has closed or crashed and deleted its log).
In addition, the sourceFile can get quite big (sometimes 100Mb+), and this program may be handling more than one log at a time.
Reading the whole log rather than line by line will most likely start consuming a fair bit of memory.
private void logCopier(string sourceFile, string destinationFile)
{
while (File.Exists(sourceFile))
{
string textLine;
using (var readerStream = File.Open(sourceFile,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite))
using (var reader = new StreamReader(readerStream))
{
while ((textLine = reader.ReadLine()) != null)
{
using (FileStream writerStream = new FileStream(destinationFile,
FileMode.Append,
FileAccess.Write))
using (StreamWriter writer = new StreamWriter(writerStream))
{
writer.WriteLine(textLine);
}
}
}
}
}
The problem is that my WPF application locks up and ceases to respond when it reaches this code.
To track down where, I put a MessageBox just before the writerStream line of the code to output what the reader was picking up.
It was certainly reading the log file just fine, but there appears to be a problem with writing it to the file.
As soon as it reaches the using (FileStream writerStream = new FileStream part of the code, it stops responding.
Is using the StreamWriter in this manner not valid, or have I just gone and dome something silly in the code?
Am also open to a better solution than what I'm trying to do here.
Simply what I understand is you need to copy a file from source to destination which may be deleted at any time.
I'll suggest you to use FileSystemWatcher to watch for source file changed event, then just simply copy the whole file from source to destination using File.Copy.
I've just solved the problem, and the issue was indeed something silly!
When creating the text file for the StreamWriter, I had forgotten to use .Dispose();. I had File.Create(filename); instead of File.Create(filename).Dispose(); This meant the text file was already open, and the StreamWriter was attempting to write to a file that was locked / in use.
The UI still locks up (as expected), as I've yet to implement this on a new thread as SteenT mentioned. However the program no longer crashes and the code correctly reads the log and outputs to a text file.
Also after a bit of refinement, my log reader/writer code now looks like this:
private void logCopier(string sourceFile, string destinationFile)
{
int num = 1;
string textLine = String.Empty;
long offset = 0L;
while (num == 1)
{
if (File.Exists(sourceFile))
{
FileStream stream = new FileStream(sourceFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using (new StreamReader(stream))
{
stream.Seek(offset, SeekOrigin.Begin);
TextReader reader2 = new StreamReader(stream);
while ((textLine = reader2.ReadLine()) != null)
{
Thread.Sleep(1);
StreamWriter writer = new StreamWriter(destinationFile, true);
writer.WriteLine(textLine);
writer.Flush();
writer.Close();
offset = stream.Position;
}
continue;
}
}
else
{
num = 0;
}
}
}
Just putting this code up here in case anyone else is looking for something like this. :)

Can't read from newly created .txt. It's being used by another process

When I create my log.txt with File.Create(Path.Combine(PATH, NAME)); and then try to read from it I get an exception: {System.IO.IOException: The process cannot access the file 'c:\temp\log.txt' because it is being used by another process..
If the log.txt file exits and not created in the method I can read and write to the log wihtout any problems.
Is the log.txt created async and the problem is that the program is trying to read it before it's created?
public static void WriteToLog(string text)
{
try
{
if (!Directory.Exists(PATH))
{
Directory.CreateDirectory(PATH);
}
if( !File.Exists(Path.Combine(PATH, NAME)) )
{
File.Create(Path.Combine(PATH, NAME));
}
var logLines = File.ReadAllLines(Path.Combine(PATH, NAME)).ToList<string>();
logLines.Insert(0, "-------------------------------------------------End New Log");
logLines.Insert(0, text);
logLines.Insert(0, "-------------------------------------------------Start New Log");
File.WriteAllLines(Path.Combine(PATH, NAME), logLines);
}
catch (Exception ex)
{
}
}
File.Create creates a filestream, which is open after the creation. so the file is used by its own process.
just change it to
using(var f = File.Create(Path.Combine(PATH, NAME))) { } ;
File.Create has a return value of type FileStream. That FileStream should be Closed (or Disposed) if you do not intend to use it for anything.
For a log file, however, I'd usually create the FileStream directly by constructing a FileStream object, using one of the constructors that accepts a FileShare parameter. That way, you can keep the stream open, but indicate that other programs should be able to open it for reading:
var fs = new FileStream(Path.Combine(PATH, NAME),
FileMode.OpenOrCreate,
FileAccess.Write,
FileShare.Read); //Now other people can access the log file whilst I'm still writing to it

Why am I getting a Unhandled Exception: System.IO.IOException when trying to read from a file being written to?

I have two C# applications, one is reading a file (File A) line by line and writing its contents to a different file (File B).
The second application is using FileSystemWatcher for File B to see when it is updated and reporting the difference is line numbers between when the program was started and when the file was changed.
Thats all I am trying to do for now, ultimately I want to read the lines between when the file was last read and the current read but until I can get the line difference that is on hold.
The code that I have for application 1 is;
static void Main(string[] args)
{
String line;
StreamReader sr = new StreamReader("f:\\watch\\input.txt");
FileStream fs = new FileStream("f:\\watch\\Chat.log", FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
StreamWriter sw = new StreamWriter(fs);
while ((line = sr.ReadLine()) != null)
{
sw.WriteLine(line);
Thread.Sleep(200);
Console.WriteLine(line);
sw.Flush();
}
sw.Close();
sr.Close();
}
The code that I have for application 2 is;
public static int lines = 0;
public static void Main()
{
Run();
}
public static void Run()
{
string[] args = System.Environment.GetCommandLineArgs();
if (args.Length != 2)
{
Console.WriteLine("Usage: Watcher.exe (directory)");
return;
}
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = args[1];
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Filter = "Chat.log";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
lines = File.ReadAllLines(args[1] + "\\Chat.log").Length;
Console.WriteLine("File lines: " + lines);
while(Console.Read()!='q');
}
private static void OnChanged(object source, FileSystemEventArgs e)
{
Linework(e.FullPath);
Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
}
public static string Linework(string path)
{
string newstring = " ";
using (FileStream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
int newlines = File.ReadAllLines(path).Length;
Console.WriteLine("Lines now: " + newlines);
}
return newstring;
}
Now when I try and run these two applications together I get an exception saying "Unhandled Exception: System.IO.IOException: The process cannot access the file because it is in use by another process".
I have both filestreams setup for ReadWrite access and I have one of the filestreams setup for FileAccess.Write and the other for FileAccess.Read.
Any clues as to why I would be getting this exception?
Thanks
Hew.
lines = File.ReadAllLines(args[1] + "\Chat.log").Length;
There's your problem. That method opens the file, reads all the lines and closes it again. It uses "normal" file share settings when opening the file, FileShare.Read. That denies write access to any other process that also has the file opened.
That cannot work here, you've already have the file opened with write access. The 2nd process cannot deny it. The IOException is the result.
You cannot use File.ReadAllLines() as-is here, you need to open a FileStream with FileShare.ReadWrite, pass it to a StreamReader and read all lines.
Beware the very troublesome race potential you've got here, there's no guarantee that the last line you'll read is a complete line. Getting only a \r and not the \n at the end of the line is a particularly tricky issue. This will strike randomly and infrequently, the hardest bugs to troubleshoot. Maybe your Flush() call fixes it, I've never been brave enough to put this to the test.
Allowing the second program ReadWrite access on the file would work in this case.
//lines = File.ReadAllLines(args[1] + "\\Chat.log").Length;
//Commenting the above lines as this would again open a new filestream on the chat.log
//without the proper access mode required.
using (FileStream fsReader = new FileStream(args[1] + "\\Chat.log", FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fsReader))
{
while (sr.ReadLine() != null)
lines++;
}
}
StreamReader sr = new StreamReader("f:\\watch\\input.txt");
input.txt might not be available for reading?
Also use the using statement instead of Close() in the 1st application (in case an exception is thrown).
Otherwise it is OK. The file share may require additional permissions though (can't really affect that).
I have missed one piece of code:
int newlines = File.ReadAllLines(path).Length;
use the stream with a StreamReader for that.
MSDN offers two ways to not obtain an exclusive hold:
A FileStream object will not have an
exclusive hold on its handle when
either the SafeFileHandle property is
accessed to expose the handle or the
FileStream object is given the
SafeFileHandle property in its
constructor.
The documentation implies that the inverse is true:
Opening a FileStream without setting the SafeFileHandle means the
FileStream maintains an exclusive hold on the file handle (which is
inline with the IO exception that is supposed to be thrown).

Can I prevent a StreamReader from locking a text file whilst it is in use?

The StreamReader locks a text file whilst it is reading it.
Can I force the StreamReader to work in a "read-only" or "non locking" mode?
My workaround would be to copy the file to a temp location and read it from there but I would prefer to use the StreamReader directly if possible.
Any alternative suggetions?
Background:
I've written a small app to get some stats out of a log file. This file is constantly being updating (several times a second) by an outside program lets call AAXXYY.
Reviewing the output suggests that my app may be locking the file and preventing AAXXYY from writing.
This is what I'm doing
private void btnGetStats_Click(object sender, EventArgs e)
{
int countStarts = 0;
int countEnds = 0;
IList<string> sessions = new List<string>();
using(StreamReader stRead = new StreamReader(openFileDialog1.FileName,Encoding.Unicode))
{
while(!stRead.EndOfStream)
{
string line = stRead.ReadLine();
if(line.Contains("Session start"))
{
countStarts++;
sessions.Add(line.Substring(line.IndexOf("["), line.LastIndexOf("]") - line.IndexOf("[")));
}
if (line.Contains("Session end"))
{
countEnds++;
sessions.Remove(line.Substring(line.IndexOf("["), line.LastIndexOf("]") - line.IndexOf("[")));
}
}
}
txtStarts.Text = countStarts.ToString();
txtEnds.Text = countEnds.ToString();
txtDifference.Text = (countStarts - countEnds).ToString();
listBox1.DataSource = sessions;
}
You can pass a FileStream to the StreamReader, and create the FileStream with the proper FileShare value. For instance:
using (var file = new FileStream (openFileDialog1.FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader (file, Encoding.Unicode)) {
}
Thought I'd add some context, StreamReader does not lock a file for reading only for writing whist it is being read. Take a look at the code below from the StreamReader class.
new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, FileOptions.SequentialScan);
Notice the default FileAccess.Read parameter taken for MSDN http://msdn.microsoft.com/en-us/library/system.io.fileshare.aspx
Allows subsequent opening of the file for reading. If this flag is not specified, any request to open the file for reading (by this process or another process) will fail until the file is closed. However, even if this flag is specified, additional permissions might still be needed to access the file.
Again taken from MSDN to allow reading and writing use FileAccess.ReadWrite instead (as suggested by Jb Evain).
Allows subsequent opening of the file for reading or writing. If this
flag is not specified, any request to open the file for reading or
writing (by this process or another process) will fail until the file
is closed. However, even if this flag is specified, additional
permissions might still be needed to access the file.

Categories