How can c# exception handling be localised without excessive nesting? - c#

I'm learning c# and struggling to write good error handling code. For example, when opening and reading a file, it seems difficult to do error handling which is localised to where the error occurs. Take the following (much simplified) example:
private static void ReadFile(string path) {
try {
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.ASCII);
var line = string.Empty;
while ((line = sr.ReadLine()) != null) {
// Actions performed on each line here
Console.WriteLine(line);
}
}
catch (IOException) {
Console.Error.WriteLine("Exception");
return;
}
}
In the code above, the catch statement will catch any IOException raised whilst opening or reading the file. In a real world application there could also be other I/O operations within the main loop which could raise the same exception.
However, I would like to handle errors raised when opening the file differently from errors whilst reading the file so I would like different error handling code for each I/O operation.
How can I narrow down the scope of the try/catch so that it just catches exceptions during file opening? If I put the using statements in their own try/catch block like this:
try {
using var fs = new FileStream(path, FileMode.Open, FileAccess.Read);
using var sr = new StreamReader(fs, Encoding.ASCII);
catch (IOException) {
// etc.
it doesn't work because they go out of scope at the end of the block.

Related

Can the compiler/JIT merge nested usings into a single try/catch block?

Is the compiler able to merge any of the following code into a single try/catch?
using (FileStream stream = new FileStream(filePath, FileMode.Open))
{
using (BinaryReader reader = new BinaryReader(stream))
{
a try/catch around my actual code...
}
}
If not, would there be any functional difference if I do this instead?
FileStream stream = null;
BinaryReader reader = null;
try
{
stream = new FileStream(filePath, FileMode.Open);
reader = new BinaryReader(stream)
// do my stuff
}
catch (stuff)
{
}
finally
{
if (stream != null)
stream.Close();
if (reader != null)
reader.Close();
}
Since the overhead of having an extra try/finally is virtually zero, there is no difference if the compiler combines two blocks in one or keeps them separate. Your translated code would have a single try-catch embedded inside two layers of try-finally, which are virtually free in terms of performance.
Converting the using code manually has a different implication - the visibility of variables is different. Your translation of nested using statements leaves stream and reader variables accessible after the block. They refer to a closed stream and a closed reader. Regular using, on the other hand, keeps its variables inside its scope, as if an additional pair of curly braces were placed around the whole block:
{ // <<==
FileStream stream = null;
BinaryReader reader = null;
try {
stream = new FileStream(filePath, FileMode.Open);
reader = new BinaryReader(stream)
// do my stuff
} finally {
if (stream != null)
stream.Close();
if (reader != null)
reader.Close();
}
} // <<==
using is better in this way, because it lets you avoid an extra level of nesting while keeping its variable in local scope. Moreover, you could reduce the level of nesting in your source by placing two consecutive using blocks together at the same level of indentation, with no curly braces:
using (FileStream stream = new FileStream(filePath, FileMode.Open))
using (BinaryReader reader = new BinaryReader(stream)) {
a try/catch around my actual code...
}
Is the compiler able to merge any of the following code into a single try/catch?
No, the compiler is not able to make your code behave differently from what you've written. And it would be different even without the possibility of Close() and Dispose() having different effects, but let's pretend you called Dispose() yourself.
If not, would there be any functional difference if I do this instead?
Yes. Putting both Dispose() invocations in a single finally block means that reader won't be disposed if stream.Dispose() throws an exception.
No well-written IDisposable's Dispose() method throws an exception, but the compiler cannot know or assume that all implementations are well-written.

Write and read from same file

I have been looking at writing a string into a file and then read it line by line, but I always get the error IOException: Sharing violation on path C:...\level1.txt. I looked on the internet and they said I should use the same stream to write and read but it didn't work,so I tried to make a different stream for each one and it didn't work either.
FileStream F = new FileStream("level1.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite);
StreamWriter sw = new StreamWriter(F);
sw.Write(Gui.codeText.Replace("\n", "\r\n"));
F.Close();
FileStream F2 = new FileStream("level1.txt", FileMode.Open, FileAccess.Read);
StreamReader file = new StreamReader(F2);
while ((line = file.ReadLine()) != null)
{ ....}
I think you just need to use the Using statement while read and write.
Since the StreamWriter and StreamReader classes inherits Stream, which is implements the IDisposable interface, the example can use using statements to ensure that the underlying file is properly closed following the write or read operations.
using (StreamWriter sw = new StreamWriter(F))
{
//Write Logic goes here...
}
using (StreamReader file = new StreamReader(F2))
{
//Read Logc goes here...
}
The Using Statement:
An using statement is translated into three parts: acquisition, usage,
and disposal. Usage of the resource is implicitly enclosed in a try
statement that includes a finally clause. This finally clause disposes
of the resource. If a null resource is acquired, then no call to
Dispose is made, and no exception is thrown.

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

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.

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. :)

Writing data from textbox into text file

Here is the code im using to write and read from text file.
StreamWriter sw1 = new StreamWriter("DataNames.txt");
sw1.WriteLine(textBox1.Text);
sw1.Close();
StreamWriter sw2 = new StreamWriter("DataNumbers.txt");
sw2.WriteLine(textBox2.Text);
sw2.Close();
FileInfo file1 = new FileInfo("DataNames.txt");
StreamReader sr1 = file1.OpenText();
while (!sr1.EndOfStream)
{
listBox1.Items.Add(sr1.ReadLine());
}
FileInfo file2 = new FileInfo("DataNumbers.txt");
StreamReader sr2 = file2.OpenText();
while (!sr2.EndOfStream)
{
listBox2.Items.Add(sr2.ReadLine());
}
The thing is that when I click my button to save data from my textboxes to my text files an error appears that says "The process cannot access the file 'C:\xxxx\xxxxxx\xxxxx\xxxx\xxxxx\xxxxx.txt' because it is being used by another process."
Can anyone tell me why I have this error and maybe help me fix it
Try added a using statment around your streams to make sure they are Disposed otherwise the file is still locked to the stream
Example:
//Write
using (StreamWriter sw1 = new StreamWriter("DataNames.txt"))
{
sw1.WriteLine(textBox1.Text);
}
using (StreamWriter sw2 = new StreamWriter("DataNumbers.txt"))
{
sw2.WriteLine(textBox2.Text);
}
// Read
foreach (var line in File.ReadAllLines("DataNames.txt"))
{
listBox1.Items.Add(line);
}
foreach (var line in File.ReadAllLines("DataNumbers.txt"))
{
listBox2.Items.Add(line);
}
It appears you do not close the file after you read it. After you call FileInfo.OpenText you get a StreamReader which has to be closed, either via Close method, or even better, with a using statement.
But there are already methods that do all that for you, have a look at File.WriteAllText,
File.AppendAllText and File.ReadAllLines methods.
You need to Close the StreamReader object once you do not need it any more. This should fix this issue.
I.e.
StreamReader sr1 = file1.OpenText();
try {
while (!sr1.EndOfStream)
{
listBox1.Items.Add(sr1.ReadLine());
}
}
finally {
sr1.Close();
}
FileInfo file2 = new FileInfo("DataNumbers.txt");
StreamReader sr2 = file2.OpenText();
try {
while (!sr2.EndOfStream)
{
listBox2.Items.Add(sr2.ReadLine());
}
}
finally {
sr2.Close();
}
You have opened files but not closed.
StreamReader sr1 = file1.OpenText();
StreamReader sr2 = file2.OpenText();
Your problem occurs, because you are not closing the stream readers.
A safer way of using external resources (the files in this case) is to embed their use in a using statement. The using statement automatically closes the resource at the end of the statement block or if the statement block if left in another way. This could be a return statement or an exception, for instance. It is guaranteed that the resource will be closed, even after an exception occurs.
You can apply the using statement on any object which implements the IDisposable interface.
// Writing to the files
using (var sw1 = new StreamWriter("DataNames.txt")) {
sw1.WriteLine(textBox1.Text);
}
using(var sw2 = new StreamWriter("DataNumbers.txt")) {
sw2.WriteLine(textBox2.Text);
}
// Reading from the files
FileInfo file1 = new FileInfo("DataNames.txt");
using (StreamReader sr1 = file1.OpenText()) {
while (!sr1.EndOfStream) {
listBox1.Items.Add(sr1.ReadLine());
}
}
FileInfo file2 = new FileInfo("DataNumbers.txt");
using (StreamReader sr2 = file2.OpenText()) {
while (!sr2.EndOfStream)
{
listBox2.Items.Add(sr2.ReadLine());
}
}
However, you can simplify the reading part like this
// Reading from the files
listBox1.Items.AddRange(File.ReadAllLines("DataNames.txt"));
listBox2.Items.AddRange(File.ReadAllLines("DataNumbers.txt"));
I've seen this behavior before - usually there's another process open that's blocking the file access. Do you have multiple development servers open in your taskbar? (Strange, yes, but I've seen it happen)

Categories