Why doesn't FileShare work as expected? - c#

While writing some code dealing with logs and files, I've discovered some baffling behaviour in windows file io. Does anyone know why this test would fail with "cannot read file" message?
[TestMethod]
public void SouldAllowReads()
{
using (var file = File.Open(_path, FileMode.Create, FileAccess.Write, FileShare.Read))
{
using (var file2 = File.Open(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
//works ok, doesn't throw
}
try
{
using (var file3 = File.Open(_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
//fails
}
}
catch (IOException)
{
Assert.Fail("cannot read file");
}
}
}
PS. _path = Path.GetTempFileName();
EDIT:
I'll mark elevener answer as correct one, but there is one thing that bothers me in this design. .NET methods such as File.ReadAllText(_path) throw exceptions, which just shouldn't happen.
For example this snipped my test would also fail assertion:
try
{
string text = File.ReadAllText(_path);
}
catch (IOException)
{
Assert.Fail("cannot read file");
}

You have var file = open with FileAccess.Write and at the same time are trying to open var file3 = with fileshare mode FileShare.Read that doesn't allows concurrent write access.

Related

C# and ClosedXML :: How to throw/catch an exception when opening a nonexistent file?

I'm a Java coder learning C# for a new project. I have the following code which opens and reads an Excel spreadsheet:
using System.IO;
using ClosedXML.Excel;
public foo(string excelFilePath)
{
var workbook = new XLWorkbook(new FileStream(excelFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
// read and write to the file, etc...
}
This all works great, but I want the code to gracefully throw an exception if the file is misplaced or misnamed. Something like this:
using System.IO;
using ClosedXML.Excel;
public foo(string excelFilePath)
{
var workbook = null;
try
{
new XLWorkbook(new FileStream(excelFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
// read and write to the file, etc...
}
catch(Exception ???)
{
System.Console.WriteLine("Gah! I couldn't find \""+excelFilePath+"\"");
}
}
I've searched online, but I can't find any examples of doing this with new XLWorkbook() Did I pick the wrong function? Thank you.
My advice would before even be throwing the exception check for the file existence, if there is a problem opening the file you can throw IO exception
if (!File.Exists(excelFilePath))
{
Console.WriteLine("Gah! I couldn't find file \"" + excelFilePath + "\"");
return;
}
var workbook = null;
try
{
new XLWorkbook(new FileStream(excelFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
// read and write to the file, etc...
}
catch (IOException ex)
{
Console.WriteLine(ex.Message);
System.Console.WriteLine("Gah! I can't open file \"" + excelFilePath + "\"");
}
}

Use FileShare.ReadWrite with HttpPostedFile in ASP.NET MVC

I am using the code below to save a posted file to a server, but that file is being read continually and need to use FileShare.ReadWrite so I don't get a locked error.
httpRequest.Files[0].SaveAs(filePath);
Below is my reading method, how can I accomplish this with the HttpPosted file is the right way with the best performance.
using (var fileStream = new FileStream(
fileLocation,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite))
{
using (var streamReader = new StreamReader(fileStream))
{
xDocument = XDocument.Parse(streamReader.ReadToEnd());
}
}
Is this my best option?
using (var memoryStream = new MemoryStream())
{
httpRequest.Files[0].InputStream.CopyTo(memoryStream);
var bytes = memoryStream.ToArray();
using (var fs = File.Open(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite))
{
fs.Write(bytes, 0, bytes.Length);
}
}
Proplem:
You want a "Write:Once, Read:Many" Lock
Assumptions :
File is small (average write opration is 5000 ms)
No other write or read oprations (Only one programe with 2 function)
You read the file a lot more than you write to it
Solution
using System;
using System.IO;
using System.Threading;
using System.Web.Mvc;
namespace stackoverflow_56307594.Controllers
{
public class HomeController : Controller
{
public ActionResult A()
{
readFile();
return View();
}
public ActionResult B()
{
writeFile();
return View();
}
private static object writeLock = new Object();
private void readFile()
{
while (!Monitor.TryEnter(writeLock, 5000)) ; //wait 5000 ms for the writeLock (serializing access)
using (var stream = new FileStream("filePath", FileMode.Open, FileAccess.Read, FileShare.Read))
using (var reader = new StreamReader(stream))
{
// active read
// xDocument = XDocument.Parse(streamReader.ReadToEnd());
}
}
private void writeFile()
{
lock (writeLock)
{
FileStream stream = null;
while (stream == null) //wait for the active read
{
try
{
stream = new FileStream("filePath", FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
// will fail if active read becase FileShare.None while (stream == null) will wait
}
}
Request.Files[0].InputStream.CopyTo(stream);
}// unlock
}
}
}
Note :
I did not test load or simply test the solution on a webserver
I only tested it on paper 😁
Refs:
locking - How long will a C# lock wait, and what if the code crashes during the lock? - Stack Overflow
c# - Deleting files in use - Stack Overflow
multithreading - Is there a way to detect if an object is locked? - Stack Overflow
Implementing Singleton in C# | Microsoft Docs
c# - Using the same lock for multiple methods - Stack Overflow
c# - Write-Once, Read-Many Lock - Stack Overflow
c# lock write once read many - Google Search
FileShare Enum (System.IO) | Microsoft Docs

FileMode and FileAccess and IOException: The process cannot access the file 'filename' because it is being used by another process

I have an application A that generates a text file for tracing.
While, an application B needs read the same text file and attach in a mailmessage.
But I get the following error, when application B try read the text file:
IOException: The process cannot access the file 'filename' because it
is being used by another process
Any suggestions ? Maybe better use for FileMode and FileAccess?
Application A
if (File.Exists(nFile2)) File.Delete(nFile2);
traceFile2 = File.Open(nFile2, FileMode.Create, FileAccess.ReadWrite, FileShare.ReadWrite);
if (traceFile2 != null)
{
var twt2 = new TextWriterTraceListener(traceFile2);
// http://www.helixoft.com/blog/archives/20
try
{
if (twt2.Writer is StreamWriter)
{
(twt2.Writer as StreamWriter).AutoFlush = true;
}
}
catch { }
var indiceTraceFile2 = Trace.Listeners.Add(twt2);
System.Diagnostics.Trace.WriteLine("INICIO: " + DateTime.Now.ToString());
Application B
using (FileStream fileStream = File.Open(f, FileMode.Open, FileAccess.ReadWrite, FileShare.Read))
{
var messageAttachment = new Attachment(fileStream, Path.GetFileName(f));
msgMail.Attachments.Add(messageAttachment);
}
You need to make sure that both the service and the reader open the log file non-exclusively. Notice line 2 of App A and Line 1 of App B
Application A:
if (File.Exists(nFile2))
File.Delete(nFile2);
traceFile2 = new FileStream(nFile2, FileMode.Open, FileAccess.Write, FileShare.ReadWrite);
if (traceFile2 != null)
{
var twt2 = new TextWriterTraceListener(traceFile2);
// http://www.helixoft.com/blog/archives/20
try
{
if (twt2.Writer is StreamWriter)
{
(twt2.Writer as StreamWriter).AutoFlush = true;
}
}
catch { }
var indiceTraceFile2 = Trace.Listeners.Add(twt2);
System.Diagnostics.Trace.WriteLine("INICIO: " + DateTime.Now.ToString());
and Application B:
using (FileStream fileStream = new FileStream(f, FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite))
{
var messageAttachment = new Attachment(fileStream, Path.GetFileName(f));
msgMail.Attachments.Add(messageAttachment);
}
Of course you can read and write from/to the same file at the same time (by different threads/processes).
Here is a sample code. Just see how FileStream is created.
string fname = "a.txt";
//WRITER
Task.Factory.StartNew(() =>
{
var f = new FileStream(fname, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
var s = new StreamWriter(f);
long l = 0;
while (true)
{
s.WriteLine(l++);
s.Flush();
Task.Delay(1000).Wait();
}
});
//READER
Task.Factory.StartNew(() =>
{
Task.Delay(1000).Wait();
var f = new FileStream(fname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var s = new StreamReader(f);
while (true)
{
var line = s.ReadLine();
if (line == null) { Task.Delay(100).Wait(); continue; };
Console.WriteLine("> " + line + " <");
}
});
It seems that you are not using the Dispose() and Close() methods of StreamWriter class to release the file.
You need to release control of the file from Program A. Try closing or disposing the streamwriter when you finish.
Or you might attempt using as is described in the answer to this question: Releasing access to files

File sharing for a separate writer and reader application in C#

I have 2 applications, one is writing to a file, and the other one reads the file. It's a log file, so the writer will be logging until the program stops, while the reader could be invoked any time to get the content of the file.
I thought that when the writer opens the file with FileShare.Read, the reader would be able to access the file, but it produces an error saying that the file is being used by another process.
Writer Application:
FileStream fs = new FileStream("file.log", FileMode.Open, FileAccess.Write, FileShare.Read);
BinaryWriter writer = new BinaryWriter(fs);
Reader Application:
BinaryReader reader = new BinaryReader(File.OpenRead("file.log"));
How do I prevent this error?
Can you try specifying FileShare.Read while reading the file also? Instead of directly using File.OpenRead use FileStream with this permission.
Also, for logging, you can use log4Net or any other free logging framework which manages logging so efficiently and we do not have to manage writing to files.
o read a locked file you are going to need to provide more flags for the FileStream.
Code such as below.
using (var reader = new FileStream("d:\\test.txt", FileMode.Open, FileAccess.Read,FileShare.ReadWrite))
{
using (var binary = new BinaryReader(reader))
{
//todo: add your code
binary.Close();
}
reader.Close();
}
This would open the file for reading only with the share mode of read write. This can be tested with a small app. (Using streamreader\write instead of binary)
static Thread writer,
reader;
static bool abort = false;
static void Main(string[] args)
{
var fs = File.Create("D:\\test.txt");
fs.Dispose();
writer = new Thread(new ThreadStart(testWriteLoop));
reader = new Thread(new ThreadStart(testReadLoop));
writer.Start();
reader.Start();
Console.ReadKey();
abort = true;
}
static void testWriteLoop()
{
using (FileStream fs = new FileStream("d:\\test.txt", FileMode.Open, FileAccess.Write, FileShare.Read))
{
using (var writer = new StreamWriter(fs))
{
while (!abort)
{
writer.WriteLine(DateTime.Now.ToString());
writer.Flush();
Thread.Sleep(1000);
}
}
}
}
static void testReadLoop()
{
while (!abort)
{
Thread.Sleep(1000);
using (var reader = new FileStream("d:\\test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var stream = new StreamReader(reader))
{
Console.WriteLine(stream.ReadToEnd());
stream.Close();
}
reader.Close();
}
}
}
I realize the example above is pretty simple but the fact still remains that the "testWriteLoop" never releases the lock.
Hope this helps

The process cannot access the file because it is being used by another process

When I execute the code below, I get the common exception The process cannot access the file *filePath* because it is being used by another process.
What is the most efficient way to allow this thread to wait until it can safely access this file?
Assumptions:
the file has just been created by me, so it is unlikely that another app is accessing it.
more than one thread from my app might be trying to run this code to append text to the file.
:
using (var fs = File.Open(filePath, FileMode.Append)) //Exception here
{
using (var sw = new StreamWriter(fs))
{
sw.WriteLine(text);
}
}
So far, the best that I have come up with is the following. Are there any downsides to doing this?
private static void WriteToFile(string filePath, string text, int retries)
{
const int maxRetries = 10;
try
{
using (var fs = File.Open(filePath, FileMode.Append))
{
using (var sw = new StreamWriter(fs))
{
sw.WriteLine(text);
}
}
}
catch (IOException)
{
if (retries < maxRetries)
{
Thread.Sleep(1);
WriteToFile(filePath, text, retries + 1);
}
else
{
throw new Exception("Max retries reached.");
}
}
}
If you have multiple threads attempting to access the same file, consider using a locking mechanism. The simplest form could be:
lock(someSharedObject)
{
using (var fs = File.Open(filePath, FileMode.Append)) //Exception here
{
using (var sw = new StreamWriter(fs))
{
sw.WriteLine(text);
}
}
}
As an alternative, consider:
File.AppendText(text);
You can set a FileShare to allow multiple access with this File.Open command like
File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)
But i think the cleanest way if you have multiple threads that are trying to write into one file would be to put all these messages into a Queue<T> and have one additional thread that writes all elements of the queue into the file.

Categories

Resources