EPPlus Open File and lock file through multiple saves - c#

I want to be able to open an Excel file (or create if it doesn't exist) and add data to it asynchronously. I have the async component working quite well using a blocking collection, though if I want to save every loop of my while statement i keep getting issues.
I can either get file corruption, or the data never saves at all. Or sometimes it only saves the first or second data segment in my two part test.
I have the following code to show a similar cut down version of my issue:
BlockingCollection<Excel_Data> collection = null;
FileStream fs = new FileStream(this.path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read);
ExcelPackage excel = new ExcelPackage(fs);
int i = 0;
while (true) {
//---- do some asyc operations
Excel_Data dict_item = collection.Take();
excel.Workbook.Worksheets.Add("sheet" + i.ToString());
//excel.Save();
excel.SaveAs(fs);
if (++i == 2) {
break;
}
}
fs.Close();
In the above example after simply create 2 sheets, the file already becomes corrupted and I am unsure how to fix this issue without going purely with FileInfo over FileStream. But then i will never be able to lock my file for writing for the duration of my app.

Related

.NET Core - Multiple read/write operations on a text file

Multiple read/write operation are access a single file, and while write operation I'm facing this issue
The process cannot access the file because it is being used in another process
Using this to add text to file
using (StreamWriter writer=System.IO.File.AppendText("wwwroot/Files/file.txt"))
{
writer.WriteLine(stringData.ToString());
writer.Close();
}
Is there anyway to perform multiple read/write on a file?
Thanks
If are using the same code from multiple threads or apps writing to the same file, you may find when one thread is wiring it finds the file already in use.
The only reason for the error message is that the file is not closed.
If the calls to write to the file are sequential from the same app then the file is not getting closed properly.
One way to handle this is to check for locked files and retry later.
something like this can be used to check if the file is open:
public static bool CanBeOpenedForExclusiveRead(string filename)
{
try
{
// Test file for exclusive read/write
using (System.IO.FileStream fileStream = System.IO.File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.None))
{
fileStream.Close();
}
return true;
}
catch
{
}
return false;
}
if you are already using something like NLog is to use nlog to "log" writes to a file, where nlog will have better handling of these issues across threads.
Try the below code. You may not need a StreamWriter for this
// To append text to file
System.IO.File.AppendAllText("FilePath", "TextToWrite");
// To read all text from file
string textFromFile = System.IO.File.ReadAllText("FilePath");

Reading file, and monitor new line

I'm looking to create a console application that will read a file, and monitor every new line since it's being write by another process every .5 seconds.
How can I achieve that, within a Console App using .NET 4.5?
It sounds like you want a version of tail for Windows. See "Looking for a windows equivalent of the unix tail command" for discussion on that.
Otherwise, open the file without preventing other processes access using FileShare.ReadWrite. Seek to the end read then use Thread.Sleep() or Task.Delay() to wait the half a second between seeing if there are any changes.
For example:
public static void Follow(string path)
{
// Note the FileShare.ReadWrite, allowing others to modify the file
using (FileStream fileStream = File.Open(path, FileMode.Open,
FileAccess.Read, FileShare.ReadWrite))
{
fileStream.Seek(0, SeekOrigin.End);
using (StreamReader streamReader = new StreamReader(fileStream))
{
for (;;)
{
// Substitute a different timespan if required.
Thread.Sleep(TimeSpan.FromSeconds(0.5));
// Write the output to the screen or do something different.
// If you want newlines, search the return value of "ReadToEnd"
// for Environment.NewLine.
Console.Out.Write(streamReader.ReadToEnd());
}
}
}
}
As #Sudhakar mentioned, FileSystemWatcher is useful when you want to be notified when a file updates sporadically, and polling at regular intervals is useful when you want to be constantly processing information from an always-growing file (such as a busy log file).
I'd like to add a note about efficiency. If you are concerned with the efficiency and speed of processing large files (many MB or GB), then you will want to track your position in the file as you read and process updates. For example:
// This does exactly what it looks like.
long position = GetMyLastReadPosition();
using (var file = File.Open(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
if (position == file.Length)
return;
file.Position = position;
using (var reader = new StreamReader(file))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// Do reading.
}
position = file.Position; // Store this somewhere too.
}
}
This should allow you to avoid reprocessing any part of a file that you have already processed.
Solution 1: You can use FileSystemWatcher class
From MSDN:
Use FileSystemWatcher to watch for changes in a specified directory.
You can watch for changes in files and subdirectories of the specified
directory. You can create a component to watch files on a local
computer, a network drive, or a remote computer.
Solution 2: You can use Polling by creating a Timer and reading the contents of the file for every 5 seconds.

Reading file after writing it

I have a strange problem. So my code follows as following.
The exe takes some data from the user
Call a web service to write(and create CSV for the data) the file at perticular network location(say \some-server\some-directory).
Although this web service is hosted at the same location where this
folder is (i.e i can also change it to be c:\some-directory). It then
returns after writing the file
the exe checks for the file to exists, if the file exists then further processing else quite with error.
The problem I am having is at step 3. When I try to read the file immediately after it has been written, I always get file not found exception(but the file there is present). I do not get this exception when I am debugging (because then I am putting a delay by debugging the code) or when Thread.Sleep(3000) before reading the file.
This is really strange because I close the StreamWriter before I return the call to exe. Now according to the documention, close should force the flush of the stream. This is also not related to the size of the file. Also I am not doing Async thread calls for writing and reading the file. They are running in same thread serially one after another(only writing is done by a web service and reading is done by exe. Still the call is serial)
I do not know, but it feels like there is some time difference between the file actually gets written on the disk and when you do Close(). However this baffling because this is not at all related to size. This happens for all file size. I have tried this with file with 10, 50, 100,200 lines of data.
Another thing which I suspected was since I was writing this file to a network location, it could be windows is optimizing the call by writing first to cache and then to network location. So I went ahead and changed the code to write it on drive(i.e use c:\some-directory), rather than network location. But it also resulted in same error.
There is no error in code(for reading and writing). As explained earlier, by putting a delay, it starts working fine. Some other useful information
The exe is .Net Framework 3.5
Windows Server 2008(64 bit, 4 GB Ram)
Edit 1
File.AppendAllText() is not correct solution, as it creates a new file, if it does not exits
Edit 2
code for writing
using (FileStream fs = new FileStream(outFileName, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(fs, Encoding.Unicode))
{
writer.WriteLine(someString)
}
}
code for reading
StreamReader rdr = new StreamReader(File.OpenRead(CsvFilePath));
string header = rdr.ReadLine();
rdr.Close();
Edit 3
used textwriter, same error
using (TextWriter writer = File.CreateText(outFileName))
{
}
Edit 3
Finally as suggested by some users, I am doing a check for the file in while loop for certain number of times before I throw the exception of file not found.
int i = 1;
while (i++ < 10)
{
bool fileExists = File.Exists(CsvFilePath);
if (!fileExists)
System.Threading.Thread.Sleep(500);
else
break;
}
So you are writing a stream to a file, then reading the file back to a stream? Do you need to write the file then post process it, or can you not just use the source stream directly?
If you need the file, I would use a loop that keeps checking if the file exists every second until it appears (or a silly amount of time has passed) - the writer would give you an error if you couldn't write the file, so you know it will turn up eventually.
Since you're writing over a network, most optimal solution would be to save your file in the local system first, then copy it to network location. This way you can avoid network connection problems. And as well have a backup in case of network failure.
Based on your update, Try this instead:
File.WriteAllText(outFileName, someString);
header = null;
using(StreamReader reader = new StreamReader(CsvFilePath)) {
header = reader.ReadLine();
}
Have you tried to read after disposing the writer FileStream?
Like this:
using (FileStream fs = new FileStream(outFileName, FileMode.Create))
{
using (StreamWriter writer = new StreamWriter(fs, Encoding.Unicode))
{
writer.WriteLine(someString)
}
}
using (StreamReader rdr = new StreamReader(File.OpenRead(CsvFilePath)))
{
string header = rdr.ReadLine();
}

File contents stops updating on windows share

I have a program that (repeatedly) reads contents of a file and, if new data arrives, do some processing. Reading is quite straightforward, something like
class Reader
{
FileStream fs_ = null;
StreamReader sr_ = null;
Reader(string filename)
{
fs_= new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete);
sr_ = new StreamReader(fs_);
}
void Read()
{
string line;
while (line = sr_.ReadLine())
{
// ...
}
}
}
Method Read() is polled every 300 ms. There is some piece of code that closes and reopens file in case of its renaming/deletion by external actors.
Generally, it works ok, but sometimes (I've encountered this two times during last month) strange thing appears. File on a share reporting correct length, but when trying to read from it, shows one and a half of string and rest of file with zeroed (0x00, not '0') contents. Moreover, I got same picture when trying to read the file via any external text/binary editor from the same machine that hosted my program. From other machines on the network file is read without any problems and shows full contents. The problem persists until I reboot the machine with my program.
Any idea what happens and how can I fix it?

Reusing a filestream

In the past I've always used a FileStream object to write or rewrite an entire file after which I would immediately close the stream. However, now I'm working on a program in which I want to keep a FileStream open in order to allow the user to retain access to the file while they are working in between saves. ( See my previous question).
I'm using XmlSerializer to serialize my classes to a from and XML file. But now I'm keeping the FileStream open to be used to save (reserialized) my class instance later. Are there any special considerations I need to make if I'm reusing the same File Stream over and over again, versus using a new file stream? Do I need to reset the stream to the beginning between saves? If a later save is smaller in size than the previous save will the FileStream leave the remainder bytes from the old file, and thus create a corrupted file? Do I need to do something to clear the file so it will behave as if I'm writing an entirely new file each time?
Your suspicion is correct - if you reset the position of an open file stream and write content that's smaller than what's already in the file, it will leave trailing data and result in a corrupt file (depending on your definition of "corrupt", of course).
If you want to overwrite the file, you really should close the stream when you're finished with it and create a new stream when you're ready to re-save.
I notice from your linked question that you are holding the file open in order to prevent other users from writing to it at the same time. This probably wouldn't be my choice, but if you are going to do that, then I think you can "clear" the file by invoking stream.SetLength(0) between successive saves.
There are various ways to do this; if you are re-opening the file, perhaps set it to truncate:
using(var file = new FileStream(path, FileMode.Truncate)) {
// write
}
If you are overwriting the file while already open, then just trim it after writing:
file.SetLength(file.Position); // assumes we're at the new end
I would try to avoid delete/recreate, since this loses any ACLs etc.
Another option might be to use SetLength(0) to truncate the file before you start rewriting it.
Recently ran into the same requirement. In fact, previously, I used to create a new FileStream within a using statement and overwrite the previous file. Seems like the simple and effective thing to do.
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write)
{
ProtoBuf.Serializer.Serialize(stream , value);
}
However, I ran into locking issues where some other process is locking the target file. In my attempt to thwart this I retried the write several times before pushing the error up the stack.
int attempt = 0;
while (true)
{
try
{
using (var stream = new FileStream(path, FileMode.Create, FileAccess.Write)
{
ProtoBuf.Serializer.Serialize(stream , value);
}
break;
}
catch (IOException)
{
// could be locked by another process
// make up to X attempts to write the file
attempt++;
if (attempt >= X)
{
throw;
}
Thread.Sleep(100);
}
}
That seemed to work for almost everyone. Then that problem machine came along and forced me down the path of maintaining a lock on the file the entire time. So in lieu of retrying to write the file in the case it's already locked, I'm now making sure I get and hold the stream open so there are no locking issues with later writes.
int attempt = 0;
while (true)
{
try
{
_stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read);
break;
}
catch (IOException)
{
// could be locked by another process
// make up to X attempts to open the file
attempt++;
if (attempt >= X)
{
throw;
}
Thread.Sleep(100);
}
}
Now when I write the file the FileStream position must be reset to zero, as Aaronaught said. I opted to "clear" the file by calling _stream.SetLength(0). Seemed like the simplest choice. Then using our serializer of choice, Marc Gravell's protobuf-net, serialize the value to the stream.
_stream.SetLength(0);
ProtoBuf.Serializer.Serialize(_stream, value);
This works just fine most of the time and the file is completely written to the disk. However, on a few occasions I've observed the file not being immediately written to the disk. To ensure the stream is flushed and the file is completely written to disk I also needed to call _stream.Flush(true).
_stream.SetLength(0);
ProtoBuf.Serializer.Serialize(_stream, value);
_stream.Flush(true);
Based on your question I think you'd be better served closing/re-opening the underlying file. You don't seem to be doing anything other than writing the whole file. The value you can add by re-writing Open/Close/Flush/Seek will be next to 0. Concentrate on your business problem.

Categories