Using TextWriter with StreamWriter and Reading/Writing Simultaneously - c#

As the title suggests, I'm attempting to read and write to a file at the same time. I have researched this topic but the answers I have found don't seem to work for me because of the circumstances in my program. I am using multiple FileSystemWatchers to track a large amount of files that are constantly passing through a flow in my network. As the files pass through each part of my flow, a text file is updated(one text file per spot in the flow) that marks the name of the file and the time it was created within the folder. It is unpredictable when files might be passing through, and when they might be writing to the tracker text files. My goal is to be able to read and write to the file simultaneously, in case a user attempts to read to from a text file that is being written to at the exact same time. How would I accomplish this?
//Write to File
private void WriteToFile(string info,string path,string tr)
{
if (!File.Exists(path+#"\"+#tr))
{
var myFile =
File.Create(path + #"\" + #tr);
myFile.Close();
TextWriter tw = new StreamWriter(path + #"\" + #tr, true);
tw.WriteLine(info,true);
tw.Close();
}
else if (File.Exists(path + #"\" + #tr))
{
TextWriter tw = new StreamWriter(path + #"\" + #tr, true);
tw.WriteLine(info);
tw.Close();
}
}

The circumstances you are alluding to seem to say that while multiple attempts can be made to read/write the file at a given time, you still want to ensure that the operations are performed one after another in the correct order that the read or writes got called.
One simple method of ensuring that the read and write operations are synchronized would be to just put a lock or Monitor around the methods. Try the following code for your write method:
private readonly object _locker = new object();
// write the file
private void WriteToFile(string info, string path, string tr)
{
Monitor.Enter(this._locker);
try
{
if (!File.Exists(path + #"\" + #tr))
{
var myFile =
File.Create(path + #"\" + #tr);
myFile.Close();
TextWriter tw = new StreamWriter(path + #"\" + #tr, true);
tw.WriteLine(info, true);
tw.Close();
}
else if (File.Exists(path + #"\" + #tr))
{
TextWriter tw = new StreamWriter(path + #"\" + #tr, true);
tw.WriteLine(info);
tw.Close();
}
}
finally
{
Monitor.Exit(this._locker);
}
}
Then, I would use a very similar construct for reading the file.
// read the file
private string ReadFile(string path)
{
Monitor.Enter(this._locker);
try
{
// read the file here...
}
finally
{
Monitor.Exit(this._locker);
}
}
What the Monitor will do is ensure that the file will not be read until an on-going write operation is complete (and vice-versa). This will ensure that you will not get the old data when you read it, and you will also not over-write the new data (which has not yet been read). This method verifies the integrity of your files at all times.

Related

Where should I close the File Stream?

I am trying to get the text of a file in C# (config) and to put it into fields. The code itself works, but I need to close the file stream to open a new one futher in the code, but don't know where to put the Close() tag. Can someone help me?
string documentspath = System.Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
string rpath = documentspath + #"\Luminous\remember.ini";
if (File.Exists(rpath))
{
try
{
string text = File.ReadAllText(rpath);
string path = text.Split('|')[0];
string process = text.Split('|')[1];
string loadLib = text.Split('|')[2];
pathBox.Text = path;
processBox.Text = process;
if (loadLib == "True")
{
loadLibrary.Checked = true;
}
else
{
manualMap.Checked = true;
}
} catch
{
MessageBox.Show("Config file corrupted. Remembered data has been lost and deleted.");
File.Delete(rpath);
}
} else
{
MessageBox.Show("No config file loaded. Welcome, " + Environment.UserName + ".");
}
EDIT: Here's the code from where I'm opening another file stream.
string documentspath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
if (!Directory.Exists(documentspath + #"\Luminous")) {
Directory.CreateDirectory(documentspath + #"\Luminous");
} else {
string rpath = documentspath + #"\Luminous\remember.ini";
if (!File.Exists(rpath)) {
File.Create(rpath);
File.WriteAllText(rpath, pathBox.Text + "|" + processBox.Text + "|" + loadlibcheck + "|" + manualmapcheck);
} else {
File.WriteAllText(rpath, string.Empty);
File.WriteAllText(rpath, pathBox.Text + "|" + processBox.Text + "|" + loadlibcheck + "|" + manualmapcheck);
}
}
You don't need to close File when using File.ReadAllText().
From MSDN:
File.ReadAllText Method Opens a text file, reads all lines of the file, and then closes the file.
Error appears because of this line:
File.Create(rpath);
Try:
using(File.Create(rpath)) {}
File.Create method actually return FileStream, which have to be disposed. Some info about it can be found on the documentation.
In section "Return Value":
Type: System.IO.FileStream A FileStream that provides read/write
access to the file specified in path.
In section "Remarks":
The FileStream object created by this method has a default FileShare value of None; no other process or code can access the created file until the original file handle is closed.
However, as Chris Dunaway mentioned, there is no need to use File.Create at all, as according to documentation WriteAllText :
Creates a new file, writes the specified string to the file, and then closes the file. If the target file already exists, it is overwritten.
You have two good options:
On the finally clause of your try/catch block.
Change your code to use using statement for your file stream.
You don't have to close anything. File.ReadAllText reads and closes a stream internally. So you can safely invoke another File.ReadAllText if you need to.

Writing files to a path with name incrementation

In a Unity3D script, I have the following that simply writes a file (an image) to a path:
var path = Application.persistentDataPath + "/ocr.jpg";
FileOperations.Create(blah, path);
Of course in a static Utility program elsewhere, I have implemented this helper function as follows:
public static void Create(byte[] bytes, string path)
{
var file = File.Open(path, FileMode.Create);
using ( var binary = new BinaryWriter(file) )
{
binary.Write(bytes);
file.Close();
}
}
I realised my implementation would only create one file; i.e. over-write an existing file with the same name (ocr.jpg) so I wrote the following code:
public static void Create(byte[] bytes, string path)
{
if ( !File.Exists(path) )
{
var file = File.Open(path, FileMode.Create);
using ( var binary = new BinaryWriter(file) )
{
binary.Write(bytes);
file.Close();
}
}
else
{
int counter = 1;
path = Application.persistentDataPath + "/ocr" + "_" + counter + ".jpg";
var file = File.Open(path, FileMode.Create);
using ( var binary = new BinaryWriter(file) )
{
binary.Write(bytes);
file.Close();
}
}
}
Then, I realised every time I run the app, the counter is set back to 1, so now my ocr_1.jpg is over-written!
(Never mind that this half-solution is already ugly, because it brings in some Unity-specific stuff. I was hoping not to include the using UnityEngine in my helper utility class.)
Next, I tried to do it recursively, as follows:
public static void Create(byte[] bytes, string path)
{
if ( !File.Exists(path) )
{
var file = File.Open(path, FileMode.Create);
using ( var binary = new BinaryWriter(file) )
{
binary.Write(bytes);
file.Close();
}
}
else
{
int counter = 1;
path = Application.persistentDataPath + "/ocr" + "_" + counter + ".jpg";
Create(bytes, path);
}
}
But I got a StackOverFlow error message. I don't understand why though, because the checking should go on until there is no file with the same name, etc...
Could someone please help me understand what I am doing wrong, and how I can achieve my goal of running the app as many times as I want and getting images created sequentially: ocr.jpg, ocr_1.jpg, ocr_2.jpg, ocr_3.jpg, etc...
Also, it would be great if I find out how to do this in a way that my utility class does not have to include unity-related stuff; i.e. only have using System.IO
In case you pass in a path that already exists and
path = Application.persistentDataPath + "/ocr" + "_" + counter + ".jpg";
also exists, you keep on calling Create method without incrementing counter, hence stack overflow. Consider passing counter value to the recursive create method, and updating it in every iteration, until it hits a filename that doesn't exist.
The reason why you are getting a StackOverflow is due to the filling of the methods Call Stack. In your code, the counter variable is a local variable to the method and is never incremented. Therefore, every time there is a recursive call to the Create method, an identical call (argument-wise) is made.
As a high-level solution, you should do two things:
Track the value of the counter throughout the recursive calls
Make sure to increment the value of the counter with every recursive call of Create
You could track the state of the counter variable either by having it as a global variable (which sometimes is not a good idea) or to keep it part of the Create method arguments.
I was hoping not to include the using UnityEngine in my helper utility
class
You don't have to. You can use its fully qualified name that includes its namespace:
UnityEngine.Application.persistentDataPath
Few things to know:
1.Always catch Exceptions when reading or writing to a file.
2.Don't do something like this: Application.persistentDataPath + "/ocr" + "_" + counter + ".jpg";.
Not a good idea to concatenate file name with "+" since that is not portable across platforms. Use Path.Combine for that.
Here is how I would implement this:
Use PlayerPrefs.SetInt to save the counter. Each time you want to save the image, use PlayerPrefs.GetInt to read the counter then save the image with File.WriteAllBytes. If saving is successful, increment the counter then save it with PlayerPrefs.SetInt.
Roughly something like below:
public static void Create(byte[] dataToSave)
{
//Load old counter
int counter = PlayerPrefs.GetInt("_counter_", 0);
//Concatenate file name
string tempPath = Path.Combine(UnityEngine.Application.persistentDataPath, "ocr");
tempPath = Path.Combine(tempPath, "_");
tempPath = Path.Combine(tempPath, counter.ToString() + ".jpg");
//Create Directory if it does not exist
if (!Directory.Exists(Path.GetDirectoryName(tempPath)))
{
Directory.CreateDirectory(Path.GetDirectoryName(tempPath));
}
//Debug.Log(path);
try
{
File.WriteAllBytes(tempPath, dataToSave);
Debug.Log("Saved Data to: " + tempPath.Replace("/", "\\"));
//Increment Counter
counter++;
//Save current Counter
PlayerPrefs.SetInt("_counter_", counter);
PlayerPrefs.Save();
}
catch (Exception e)
{
Debug.LogWarning("Failed To PlayerInfo Data to: " + tempPath.Replace("/", "\\"));
Debug.LogWarning("Error: " + e.Message);
}
}

File is in use by another process with SteamWriter [duplicate]

I'm trying to detect if a file exists at runtime, if not, create it. However I'm getting this error when I try to write to it:
The process cannot access the file 'myfile.ext' because it is being used by another process.
string filePath = string.Format(#"{0}\M{1}.dat", ConfigurationManager.AppSettings["DirectoryPath"], costCentre);
if (!File.Exists(filePath))
{
File.Create(filePath);
}
using (StreamWriter sw = File.AppendText(filePath))
{
//write my text
}
Any ideas on how to fix it?
File.Create(FilePath).Close();
File.WriteAllText(FileText);
I want to update this answer to say that this is not really the most efficient way to write all text. You should only use this code if you need something quick and dirty.
I was a young programmer when I answered this question, and back then I thought I was some kind of genius for coming up with this answer.
The File.Create method creates the file and opens a FileStream on the file. So your file is already open. You don't really need the file.Create method at all:
string filePath = #"c:\somefilename.txt";
using (StreamWriter sw = new StreamWriter(filePath, true))
{
//write to the file
}
The boolean in the StreamWriter constructor will cause the contents to be appended if the file exists.
When creating a text file you can use the following code:
System.IO.File.WriteAllText("c:\test.txt", "all of your content here");
Using the code from your comment. The file(stream) you created must be closed. File.Create return the filestream to the just created file.:
string filePath = "filepath here";
if (!System.IO.File.Exists(filePath))
{
System.IO.FileStream f = System.IO.File.Create(filePath);
f.Close();
}
using (System.IO.StreamWriter sw = System.IO.File.AppendText(filePath))
{
//write my text
}
FileStream fs= File.Create(ConfigurationManager.AppSettings["file"]);
fs.Close();
File.Create returns a FileStream. You need to close that when you have written to the file:
using (FileStream fs = File.Create(path, 1024))
{
Byte[] info = new UTF8Encoding(true).GetBytes("This is some text in the file.");
// Add some information to the file.
fs.Write(info, 0, info.Length);
}
You can use using for automatically closing the file.
I updated your question with the code snippet. After proper indenting, it is immediately clear what the problem is: you use File.Create() but don't close the FileStream that it returns.
Doing it that way is unnecessary, StreamWriter already allows appending to an existing file and creating a new file if it doesn't yet exist. Like this:
string filePath = string.Format(#"{0}\M{1}.dat", ConfigurationManager.AppSettings["DirectoryPath"], costCentre);
using (StreamWriter sw = new StreamWriter(filePath, true)) {
//write my text
}
Which uses this StreamWriter constructor.
I know this is an old question, but I just want to throw this out there that you can still use File.Create("filename")", just add .Dispose() to it.
File.Create("filename").Dispose();
This way it creates and closes the file for the next process to use it.
This question has already been answered, but here is a real world solution that
checks if the directory exists and adds a number to the end if the text file
exists. I use this for creating daily log files on a Windows service I wrote. I
hope this helps someone.
// How to create a log file with a sortable date and add numbering to it if it already exists.
public void CreateLogFile()
{
// filePath usually comes from the App.config file. I've written the value explicitly here for demo purposes.
var filePath = "C:\\Logs";
// Append a backslash if one is not present at the end of the file path.
if (!filePath.EndsWith("\\"))
{
filePath += "\\";
}
// Create the path if it doesn't exist.
if (!Directory.Exists(filePath))
{
Directory.CreateDirectory(filePath);
}
// Create the file name with a calendar sortable date on the end.
var now = DateTime.Now;
filePath += string.Format("Daily Log [{0}-{1}-{2}].txt", now.Year, now.Month, now.Day);
// Check if the file that is about to be created already exists. If so, append a number to the end.
if (File.Exists(filePath))
{
var counter = 1;
filePath = filePath.Replace(".txt", " (" + counter + ").txt");
while (File.Exists(filePath))
{
filePath = filePath.Replace("(" + counter + ").txt", "(" + (counter + 1) + ").txt");
counter++;
}
}
// Note that after the file is created, the file stream is still open. It needs to be closed
// once it is created if other methods need to access it.
using (var file = File.Create(filePath))
{
file.Close();
}
}
I think I know the reason for this exception. You might be running this code snippet in multiple threads.
you can just use using keyword around File.Create(path) to finalize the process
using(File.Create(path));
Try this: It works in any case, if the file doesn't exists, it will create it and then write to it. And if already exists, no problem it will open and write to it :
using (FileStream fs= new FileStream(#"File.txt",FileMode.Create,FileAccess.ReadWrite))
{
fs.close();
}
using (StreamWriter sw = new StreamWriter(#"File.txt"))
{
sw.WriteLine("bla bla bla");
sw.Close();
}

When to delete generated file using asp.net

I have a template excel file to generate excel files from it.
My code is as follows (This part is to create a new excel file from the template):
string currentFN = PropertyFinalResult[0].Fecha;
string fixCurrentFN = currentFN.Replace('/', '_');
string currentTime = DateTime.Now.ToLongTimeString();
string fixCurrentTime = currentTime.Replace(':', '_');
string addToFileName = fixCurrentTime.Replace(' ', '_');
string newFN = fixCurrentFN + "-" + addToFileName;
string SourceFile = Request.PhysicalApplicationPath + "Template\\ExcelTemplate.xlsx";
string DestFile = Request.PhysicalApplicationPath + "Template\\" + newFN + ".xlsx";
//To keep FileName for posterior deletion
Session["sDestFile"] = DestFile;
try
{
File.Copy(SourceFile, DestFile);
}
catch (Exception ex)
{
lblErrorSavingToDB.Text = "Error: " + ex.Message;
lblErrorSavingToDB.Visible = true;
}
after that I open the new excel file, insert the records in it and then, stream the file to the user by doing this:
//Streaming file to client
string fileName = newFN + ".xlsx";
Response.Redirect("../Template/" + fileName);
Now, my question is, whether the user save or not the file, when should I delete the generated file? I would prefer once the user closes the popup window regarding Open or Save the file. But how to know when the user closes that window?
You can use TransmitFile and then close once the transmission is over. Example:
try
{
Response.ContentType = "application/octet-stream";
Response.AddHeader("content-disposition", "attachment;filename=\"" + Path.GetFileName(path.FullName) + "\"");
Response.AddHeader("content-length", path.Length.ToString());
Response.TransmitFile(path.FullName);
Response.Flush();
}
finally
{
File.Delete(Server.MapPath("~/"+tpacode+".zip"));
}
When to delete the files (or maybe it's better to say "how long to keep the files") is a question that is best answered by your application's business rules.
In the past, in low-traffic applications, I've used a "clean-up" routine to delete files older than a certain threshold. That clean-up gets performed when a new file is created, and at that time any file in the designated folder that was older than the threshold would be deleted.

Files are not created when i run this code

I have the following code:
List<String> suma = new List<String>();
if (File.Exists(Application.StartupPath + "/totalsold" + username))
suma = new List<String>(File.ReadAllLines(Application.StartupPath + "/totalsold" + username));
List<String> actual = new List<String>();
if (File.Exists(Application.StartupPath + "/totalsold" + username))
actual = new List<String>(File.ReadAllLines(Application.StartupPath + "/soldproducts" + username));
List<String> sumatotal = new List<String>();
if (File.Exists(Application.StartupPath + "/totalsoldadmin"))
sumatotal = new List<String>(File.ReadAllLines(Application.StartupPath + "/totalsoldadmin"));
StreamWriter vanzare = new StreamWriter(Application.StartupPath + "/soldproducts" + username);
StreamWriter total = new StreamWriter(Application.StartupPath + "/totalsold" + username);
StreamWriter totall = new StreamWriter(Application.StartupPath + "/totalsoldadmin");
Why the files vanzare,total and totall are not created after the code below is executed ?
vanzare.WriteLine("Hello World");
total.WriteLine("Helle World again!");
totall.WriteLine("Hello World again and again!");
Problem solved!
Do you close the file? Writes to files may not immediately occur, since probably both .NET and the OS are caching and thus delaying writes. The file should appear immediately when you open the StreamWriter, though.
For short-term usage of a file (e.g. for writing stuff and then closing it) you should definitely use the using statement:
using (StreamWriter vanzare = new StreamWriter(...)) {
vanzare.WriteLine("Hello World");
}
which will make sure to properly close the file immediately afterwards and not leave any unmanaged resources around longer than they are needed.
If you need to leave the file open for longer than a single method, then of course you have to do that manually. Make sure that when you no longer need the StreamWriter (and other IDisposables) you call the Dispose() method on them.
You are using slashes im your file paths, which might not work, depending on what platform you are using.
Use the Path.Combine method to concatenate the path and the filename, it will use the path separator that is correct for the file system:
string sold = Path.Combine(Application.StartupPath, "totalsold" + username);
if (File.Exists(sold)) {
suma = new List<String>(File.ReadAllLines(sold));
}

Categories

Resources