modernzombie
modernzombie

Reputation: 2049

C# IO Reading and Writing file in use error

I have a library that handles reading and writing a cache file. This library is used by a Windows Service and several instances of a console application on the same machine. The console application runs when a user logs in.

I am getting occasional IO errors saying the cache file is in use by another process. I assume that collisions are occurring between the different application instances and service trying to read and write at the same time.

Is there a way to lock the file when it is in use and force all other requests to "wait in line" to access the file?

    private void SaveCacheToDisk(WindowsUser user) {
        string serializedCache = SerializeCache(_cache);
        //encryt
        serializedCache = AES.Encrypt(serializedCache);

        string path = user == null ? ApplicationHelper.CacheDiskPath() :
            _registry.GetCachePath(user);
        string appdata = user == null ? ApplicationHelper.ClientApplicationDataFolder() :
            _registry.GetApplicationDataPath(user);

        if (Directory.Exists(appdata) == false) {
            Directory.CreateDirectory(appdata);
        }

        if (File.Exists(path) == false) {
            using (FileStream stream = File.Create(path)) { }
        }

        using (FileStream stream = File.Open(path, FileMode.Truncate)) {
            using (StreamWriter writer = new StreamWriter(stream)) {
                writer.Write(serializedCache);
            }
        }
    }

    private string ReadCacheFromDisk(WindowsUser user) {
        //cache file path
        string path = user == null ? ApplicationHelper.CacheDiskPath() :
            _registry.GetCachePath(user);

        using (FileStream stream = File.Open(path, FileMode.Open)) {
            using (StreamReader reader = new StreamReader(stream)) {
                string serializedCache = reader.ReadToEnd();
                //decrypt
                serializedCache = AES.Decrypt(serializedCache);

                return serializedCache;
            }
        }
    }

Upvotes: 2

Views: 2020

Answers (4)

Rob
Rob

Reputation: 45799

One option you could consider is having the console applications route their file access through the service, that way there's only one process accessing the file and you can synchronise access to it there.

One way of implementing this is by remoting across an IPC channel (and here's another example from weblogs.asp.net). We used this technique in a project for the company I work for and it works well, with our specific case providing a way for a .net WebService to talk to a Windows Service running on the same machine.

Sample based on the weblogs.asp.net example

Basically what you need to do with the code below is create a Solution, add two Console Apps (one called "Server" and the other called "Client" and one Library to it. Add a reference to the Library to both console apps, paste the code below in and add a reference to System.Runtime.Remoting to both Server & Console.

Run the Server app, then run the client app. Observe the fact that the server app has a message passed to it by the client. You can extend this to any number of messages/tasks

// Server:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
namespace RemotingSample
{
    public class Server
    {
        public Server()
        { 
        }
        public static int Main(string[] args)
        {
            IpcChannel chan = new IpcChannel("Server");
            //register channel
            ChannelServices.RegisterChannel(chan, false);
            //register remote object
            RemotingConfiguration.RegisterWellKnownServiceType(
                        typeof(RemotingSample.RemoteObject),
                   "RemotingServer",
                   WellKnownObjectMode.SingleCall);

            Console.WriteLine("Server Activated");
            Console.ReadLine();
            return 0;
        }
    }
}

// Client:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Ipc;
using RemotingSample;
namespace RemotingSample
{
    public class Client
    {
        public Client()
        { 
        }
        public static int Main(string[] args)
        {
            IpcChannel chan = new IpcChannel("Client");
            ChannelServices.RegisterChannel(chan);
            RemoteObject remObject = (RemoteObject)Activator.GetObject(
                        typeof(RemotingSample.RemoteObject),
                        "ipc://Server/RemotingServer");
            if (remObject == null)
            {
                Console.WriteLine("cannot locate server");
            }
            else
            {
                remObject.ReplyMessage("You there?");
            }
            return 0;
        }
    }
}

// Shared Library:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;

namespace RemotingSample
{
    public class RemoteObject : MarshalByRefObject
    {
        public RemoteObject()
        {
            Console.WriteLine("Remote object activated");
        }
        public String ReplyMessage(String msg)
        {
            Console.WriteLine("Client : " + msg);//print given message on console
            return "Server : I'm alive !";
        }
    }
}

Upvotes: 1

Corbin March
Corbin March

Reputation: 25734

You could use a cross-process EventWaitHandle. This lets you create and use a WaitHandle that's identified across processes by name. A thread is notified when it's its turn, does some work, and then indicates it's done allowing another thread to proceed.

Note that this only works if every process/thread is referring to the same named WaitHandle.

The EventWaitHandle constructors with strings in their signature create named system synchronization events.

Upvotes: 1

Michael Meadows
Michael Meadows

Reputation: 28426

Check out the TextWriter.Synchronized method.

http://msdn.microsoft.com/en-us/library/system.io.textwriter.synchronized.aspx

This should let you do this:

TextWriter.Synchronized(writer).Write(serializedCache);

Upvotes: 0

jason
jason

Reputation: 241789

Sure, you could use a mutex and permit access only when holding the mutex.

Upvotes: 5

Related Questions