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
Related
I want to read a binary file line by line (I'm writing of course continously, but I know that after 457 bytes new data start and I know exactly the byte structure and where which information is written to) and change a special entry of the line. I get an System.IO.IOException when I try to access the same file with both BinaryReader and BinaryWriter. I use locking to prevent that the file is accessed from somewhere else.
My code is:
using (FileStream fs2 = new FileStream(testfile, FileMode.Open, FileAccess.Read))
{
using (BinaryReader r = new BinaryReader(fs2))
{
using (BinaryWriter bw = new BinaryWriter(new FileStream(testfile, FileMode.Open, FileAccess.Write), utf8))
{
for (int i = 0; i < 11000; i+=457)
{
int myint = r.ReadInt64();
bw.Seek(i, SeekOrigin.Current);
bw.Write(myint*2);
}
}
}
}
How can I do this?
Do not create the second FileStream because the file is locked for the read operation by the first FileStream object.
If you are sure about file structure, the exception only can come out from 2nd FileStream instantiation. See link below for more information:
Read and Write to File at the same time
It is working for me using the following code:
if (File.Exists(testfile))
{
FileInfo fi = new FileInfo(testfile);
using (FileStream fs2 = new FileStream(testfile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (BinaryReader r = new BinaryReader(fs2))
{
r.BaseStream.Seek(0, SeekOrigin.Begin);
using (BinaryWriter bw = new BinaryWriter(new FileStream(testfile, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)))
{
for (int i = 0; i <= (fi.Length-177); i += 177)//181
{
}
}
}
}
}
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
This question already has answers here:
How to both read and write a file in C#
(6 answers)
Closed 5 years ago.
I need to read the content of a file and overwrite it while the file is locked. I don't want the file to be unlocked between read and write operations.
using (var file = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
using (var reader = new StreamReader(file, Encoding.Unicode))
using (var writer = new StreamWriter(file, Encoding.Unicode))
{
// read
// calculate new content
// overwrite - how do I do this???
}
}
If I use two FileStreams, the file is cleared when instantiating the writer but the file will be briefly unlocked between the reader and writer instantiation.
using (var reader = new StreamReader(new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read, FileShare.None)))
{
// read
// calculate new content
}
using (var writer = new StreamWriter(new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.None)))
{
// write
}
If you keep open the original FileStream you can do it:
using (var file = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None))
{
// This overload will leave the underlying stream open
using (var reader = new StreamReader(file, Encoding.Unicode, true, 4096, true))
{
//Read
}
file.SetLength(0); //Truncate the file and seek to 0
using (var writer = new StreamWriter(file, Encoding.Unicode))
{
//Write the new data
}
}
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.
In Linux, a lot of IPC is done by appending to a file in 1 process and reading the new content from another process.
I want to do the above in Windows/.NET (Too messy to use normal IPC such as pipes). I'm appending to a file from a Python process, and I want to read the changes and ONLY the changes each time FileSystemWatcher reports an event. I do not want to read the entire file content into memory each time I'm looking for changes (the file will be huge)
Each append operation appends a row of data that starts with a unique incrementing counter (timestamp+key) and ends with a newline.
using (FileStream fs = new FileStream
(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (StreamReader sr = new StreamReader(fs))
{
while (someCondition)
{
while (!sr.EndOfStream)
ProcessLinr(sr.ReadLine());
while (sr.EndOfStream)
Thread.Sleep(100);
ProcessLinr(sr.ReadLine());
}
}
}
this will help you read only appended lines
You can store the offset of the last read operation and seek the file to that offset when you get a changed file notification. An example follows:
Main method:
public static void Main(string[] args)
{
File.WriteAllLines("test.txt", new string[] { });
new Thread(() => ReadFromFile()).Start();
WriteToFile();
}
Read from file method:
private static void ReadFromFile()
{
long offset = 0;
FileSystemWatcher fsw = new FileSystemWatcher
{
Path = Environment.CurrentDirectory,
Filter = "test.txt"
};
FileStream file = File.Open(
"test.txt",
FileMode.Open,
FileAccess.Read,
FileShare.Write);
StreamReader reader = new StreamReader(file);
while (true)
{
fsw.WaitForChanged(WatcherChangeTypes.Changed);
file.Seek(offset, SeekOrigin.Begin);
if (!reader.EndOfStream)
{
do
{
Console.WriteLine(reader.ReadLine());
} while (!reader.EndOfStream);
offset = file.Position;
}
}
}
Write to file method:
private static void WriteToFile()
{
for (int i = 0; i < 100; i++)
{
FileStream writeFile = File.Open(
"test.txt",
FileMode.Append,
FileAccess.Write,
FileShare.Read);
using (FileStream file = writeFile)
{
using (StreamWriter sw = new StreamWriter(file))
{
sw.WriteLine(i);
Thread.Sleep(100);
}
}
}
}