在.NET中写入Web服务中单个文件的问题
我已经在.net 2.0,C#中创建了一个Web服务。每当Web服务客户端调用不同的方法时,我都需要将一些信息记录到文件中。
当一个用户进程正在写入文件而另一进程试图写入文件时,就会出现问题。我收到以下错误:
The process cannot access the file because it is being used by another process.
我尝试在Cand中实施失败的解决方案如下。
- 实现的单例类,其中包含写入文件的代码。
- 使用lock语句来包装写入文件的代码。
- 我也尝试过使用开源记录器log4net,但这也不是一个完美的解决方案。
- 我知道有关登录系统事件记录器的信息,但是我没有选择。
我想知道是否存在一个完美而完整的解决方案来解决这一问题?
解决方案
也许会写一些"队列行"来写入文件,所以当我们尝试写入文件时,它会不断检查文件是否被锁定,如果它一直处于等待状态,如果未被锁定,则写入做到这一点。
我们可以将结果推送到MSMQ队列中,并让Windows服务从队列中选取项目并进行记录。它有点沉重,但应该可以。
锁定可能失败,因为Web服务由多个工作进程运行。
我们可以使用命名的互斥锁来保护访问,该互斥锁在进程之间共享,与使用`lock(someobject){...}获得的锁不同:
Mutex lock = new Mutex("mymutex", false); lock.WaitOne(); // access file lock.ReleaseMutex();
乔尔和查尔斯。那很快! :)
乔尔(Joel):当我们说"队列行"时,意思是创建一个单独的线程并在循环中运行以继续检查队列以及在文件未锁定时写入文件吗?
查尔斯:我了解MSMQ和Windows服务的组合,但是就像我说的那样,除了从Web服务中写入文件外,我别无选择:)
谢谢
pradeep_tp
到目前为止,所有尝试的问题都在于,多个线程可以输入代码。
那是多个线程试图获取并使用文件处理程序,因此,我们需要在工作线程之外的单个线程进行错误处理,并保持打开状态的单个文件句柄起作用。
可能最简单的操作是在Global.asax中的应用程序启动期间创建一个线程,并让该线程侦听同步的内存中队列(System.Collections.Generics.Queue)。打开线程并拥有文件句柄的生存期,只有该线程才能写入文件。
ASP中的客户端请求将暂时锁定队列,将新的日志记录消息推入队列,然后解锁。
当消息到达队列时,记录器线程将定期轮询队列以查找新消息,该线程将读取数据并将其分发到文件中。
我们没有说Web服务的托管方式,所以我假设它在IIS中。我认为除非服务在多个应用程序池中运行,否则不应由多个进程访问该文件。不过,我想当一个进程中的多个线程试图写入时,我们可能会收到此错误。
我认为我会寻求我们自己建议的解决方案,Pradeep,构建一个将所有内容写入日志文件的对象。在该对象内部,我有一个队列,所有要记录的数据都写入其中。我将有一个单独的线程从此队列中读取并写入日志文件。在像IIS这样的线程池托管环境中,创建另一个线程似乎不太好,但是它只是一个。当IIS进程关闭时,我们可能会丢失一些"正在进行中"的条目。
当然,其他选择还包括使用单独的过程(例如服务)写入文件,但这会带来额外的部署开销和IPC成本。如果那对我们不起作用,请选择单身人士。
要知道我要在代码中做什么,以下是我在C#中实现的单调类
公共密封类FileWriteTest
{
private static volatile FileWriteTest instance; private static object syncRoot = new Object(); private static Queue logMessages = new Queue(); private static ErrorLogger oNetLogger = new ErrorLogger(); private FileWriteTest() { } public static FileWriteTest Instance { get { if (instance == null) { lock (syncRoot) { if (instance == null) { instance = new FileWriteTest(); Thread MyThread = new Thread(new ThreadStart(StartCollectingLogs)); MyThread.Start(); } } } return instance; } } private static void StartCollectingLogs() { //Infinite loop while (true) { cdoLogMessage objMessage = new cdoLogMessage(); if (logMessages.Count != 0) { objMessage = (cdoLogMessage)logMessages.Dequeue(); oNetLogger.WriteLog(objMessage.LogText, objMessage.SeverityLevel); } } } public void WriteLog(string logText, SeverityLevel errorSeverity) { cdoLogMessage objMessage = new cdoLogMessage(); objMessage.LogText = logText; objMessage.SeverityLevel = errorSeverity; logMessages.Enqueue(objMessage); }
}
当我在调试模式下运行此代码(仅模拟一个用户访问权限)时,在队列出队的那一行出现错误"堆栈溢出"。
注意:在上面的代码中,ErrorLogger是一个类,具有可写入文件的代码。 objMessage是一个携带日志消息的实体类。
另外,我们可能要在登录数据库时执行错误操作(如果使用的是一个)
柯斯
我已经实现了互斥锁,它消除了"堆栈溢出"错误。在得出结论在所有情况下是否都可以正常工作之前,我还必须进行负载测试。
我在其中一个网站上阅读了有关互斥对象的信息,说互斥会影响性能。我想通过互斥锁了解一件事。
假设用户Process1正在写入文件,同时用户Process2尝试写入同一文件。由于Process1锁定了代码块,因此Process2将继续尝试还是在第一次尝试后就死掉?
谢谢
pradeep_tp
它将等待,直到释放互斥锁为止。
Joel: When you say "queue line" do you mean creating a separate thread that runs in a loop to keep checking the queue as well as write to a file when it is not locked?
是的,那基本上就是我的想法。拥有另一个具有while循环的线程,直到它可以访问文件并保存,然后结束。
但是,我们必须以开始查找的第一个线程首先获得访问权的方式进行操作。这就是为什么我说队列。