Reputation: 2049
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
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
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
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