Pergunta

I am trying to implement some basic config-file read/write/edit-funtctionality in a class. The config-file is stored in the file [...]\config.txt, which is stored in the 'path'-variable.

Syntax of my configfile is as follows:

param0 value_of_it
param1 value_of_it
something_else you_get_it

Everything left of the first space is the name by which the parameter is to be found, the rest of the line is considered the value of that param.

So much for context. Now, when I am trying to delete that file using file.delete I get:

"System.IO.IOException: Cannot access file, because it is being used by another process".

Here is the code in question:

    internal void alter(string param, bool replace = false, string value = null) {
    //here
        string buf;

        System.IO.StreamReader reader = new System.IO.StreamReader(path);

        string bufPath = path.Substring(0, path.Length-4)+"_buf.txt";

        System.IO.StreamWriter writer = new StreamWriter(bufPath);

        while((buf = reader.ReadLine()) != null) {
            if(buf.Substring(0, param.Length) != param)
                writer.WriteLine(buf);
            else if(replace)
                writer.WriteLine(param+" "+value);
        }
        writer.Dispose();
        reader.Dispose();
        writer.Close();
        reader.Close();
        //there



        File.Delete(path);
        File.Move(bufPath, path);
    }

The function copies the original file line by line into the buffer, except for the param specified in the call. That line either gets ignored, thus deleted, or replaced if the call says so. Then the original file is deleted and the _buf version is "moved" there (renamed).

These steps are executed correctly (the _buf-file gets created and correctly filled) until 'File.Delete(path)' which is where the exception is thrown.

I already tried commenting out the entire part between //here and //there and only deleting the file, which results in the same exception, so the problem has to be of more basic nature.

I tried finding a process having a problematic handle using Sysinternals Process Explorer as suggested here on ServerFault, but couldn't find anything. I only found three handles for that file in my own process. I also ran the app after killing the windows explorer, because I've read the sometimes it is the offender; That didn't help either.

I also checked the entire rest of my program and confirmed the following:

  1. This is the first occasion this file is used, so forgotten handles from the past are impossible.

  2. It is a single-threaded application (so far), so deadlocks and race-conditions are ruled out as well.

So here is my question: How can I find what is locking my file? Alternatively: Is there a simpler way to delete or edit a line in a file?

Foi útil?

Solução

It will be more convenient to store configuration in xml format, because you have options to store more complex value if you need to do so later. And also you can rely on XDocument for loading and saving the file as simple as XDocument.Load(path) and doc.Save(path). For example, your current configuration file will look like following in xml format :

<?xml version="1.0" encoding="utf-8"?>
<params>
  <param name="param0">value_of_it</param>
  <param name="param1">value_of_it</param>
  <param name="something_else">you_get_it</param>
</params>

And your function to alter existing param's value or add new parameter to configuration file will be like so :

internal void alterXml(string param, bool replace = false, string value = null)
{
    //load xml configuration file to XDocument object
    var doc = XDocument.Load(path);
    //search for <param> having attribute "name" = param
    var existingParam = doc.Descendants("param").FirstOrDefault(o => o.Attribute("name").Value == param);
    //if such a param element doesn't exist, add new element
    if (existingParam == null)
    {
        var newParam = new XElement("param");
        newParam.SetAttributeValue("name", param);
        newParam.Value = "" + value;
        doc.Root.Add(newParam);
    }
    //else update element's value
    else if (replace) existingParam.Value = "" + value;
    //save modified object back to xml file
    doc.Save(path);
}

Outras dicas

If there are three handles to that file in your process, then you are opening that file multiple times, or calling the function multiple times with the handle being left open, or you have ghost versions of your processing hanging around. You should double check that you are not opening this file elsewhere in your code. When I run your code in a test, it runs fine (if I don't cause the bug related to the Substring function below).

Because the file at "path" is opened in a read-mode, it will be able to be opened by other readers without problems. However, as soon as other code tries a write operation (including file metadata, such as File.Delete), you will see this error.

Your code is not exception safe, so an exception thrown by this, or a similar function while you are reading from the stream in that loop will cause the handle to stay open, causing a later call to a function that opens the file to fail with exception you are now experiencing at File.Delete. To avoid exception safety issues, use try/finally or using (I'll provide an example below).

One such exception that is likely to occur is where you call Substring within the loop, because there is a chance the length of the line is less than the length of a full parameter. There's also another issue here in that if you pass the parameter without a trailing space, then it's possible that you will match another parameter that contains the first as a prefix (i.e. "hello" would match "hello_world").

Here's a slightly fixed version of the code that is exception safe and fixes the Substring issue.

internal void alter( string param, bool replace = false, string value = null )
{
    //here
    string buf;
    string bufPath = path.Substring( 0, path.Length - Path.GetExtension( path ).Length ) + "_buf.txt";

    using ( System.IO.StreamReader reader = new System.IO.StreamReader( path ) )
    {
        string paramWithSpace = param + " ";

        using ( System.IO.StreamWriter writer = new StreamWriter( bufPath ) )
        {
            while ( ( buf = reader.ReadLine() ) != null )
            {
                if ( !buf.StartsWith( paramWithSpace ) )
                    writer.WriteLine( buf );
                else if ( replace )
                    writer.WriteLine( paramWithSpace + value );
            }
        }
    }
        //there

    File.Delete( path );
    File.Move( bufPath, path );
}

However, you may wish to consider loading your configuration entirely into memory, altering it in memory multiple times and then writing back to it once in a batch. This will give you greater performance reading/writing configuration (and usually you have to write multiple configuration changes at once) and it will also simplify your code. For a simple implementation, use the generic Dictionary class.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top