我有一些修改文件的线程,我试图在这之前检查文件是否正在使用 - 以防止竞争条件。
我正在使用这种方法:
protected virtual bool IsFileLocked(FileInfo file)
{
FileStream stream = null;
try
{
stream = file.Open(FileMode.Open, FileAccess.ReadWrite, FileShare.None);
}
catch (IOException)
{
//the file is unavailable because it is:
//still being written to
//or being processed by another thread
//or does not exist (has already been processed)
return true;
}
finally
{
if (stream != null)
stream.Close();
}
//file is not locked
return false;
}
并且这样称呼它:
while (flag)
{
if (IsFileLocked(new FileInfo(logPath)))
{
File.AppendAllText(logPath, Environment.NewLine + "Test");
flag = false;
}
}
问题是一个进程设法写入文件,但如果另一个进程同时写入,则不写入
答案 0 :(得分:1)
使用文件锁定并不容易,文件流允许您锁定文件的字节范围,这是过度编程以附加文本以进行记录。
在同一进程中同步写入。
private static object lockObject = new object();
public static void AppendToFile(string fileName, string text){
lock(lockObject){
File.AppendAllText(fileName, text);
}
}
要在多个进程中同步写入,您必须使用Mutex来避免竞争条件。
public static void AppendToFile(string fileName, string text){
var gl = new GlobalNamedLock(fileName);
if(!gl.Lock()){
// should not happen
// but still throw the exception some how to notice
throw new InvalidOperationException("Could not acquire lock");
}
try{
File.AppendAllText(fileName, text);
}finally{
gl.Unlock();
}
}
public class GlobalNamedLock
{
private Mutex mtx;
public GlobalNamedLock(string strLockName)
{
//Name must be provided!
if (string.IsNullOrWhiteSpace(strLockName))
{
//Use default name
strLockName = ((GuidAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(GuidAttribute), false).GetValue(0)).Value.ToString();
}
//Create security permissions for everyone
//It is needed in case the mutex is used by a process with
//different set of privileges than the one that created it
//Setting it will avoid access_denied errors.
MutexSecurity mSec = new MutexSecurity();
mSec.AddAccessRule(new MutexAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null),
MutexRights.FullControl, AccessControlType.Allow));
//Create the global mutex
bool bCreatedNew;
mtx = new Mutex(false, @"Global\" + strLockName, out bCreatedNew, mSec);
}
public bool Lock()
{
return mtx.WaitOne();
}
public void Unlock()
{
//Release it
mtx.ReleaseMutex();
}
}
答案 1 :(得分:1)
您应该将执行实际写入的代码放入单独的方法并同步,例如:
private object WRITELOCK = new object();
private void AppendToFile(string fileName, string textToAppend)
{
lock (WRITELOCK)
{
File.AppendAllText(fileName, textToAppend);
}
}
然后所有线程都会调用AppendToFile
。请注意,您仍然需要捕获线程中的异常并重试,但这样一来,该文件一次只能由一个线程写入。
线程代码应如下所示:
try
{
AppendToFile(fileName, "Hello");
}
catch (...)
{
// Handle errors writing to file
}
答案 2 :(得分:0)
你仍然有竞争条件:
while (flag)
{
if (IsFileLocked(new FileInfo(logPath)))
{
// Because IsFileLocked has closed the file,
// another thread can open the file here before
// File.AppendAllText opens the file.
File.AppendAllText(logPath, Environment.NewLine + "Test");
flag = false;
}
}
执行此操作的正确方法是打开文件,然后使用相同的流写入该文件,如果文件打开失败并重试,则捕获任何异常。在你的情况下,最简单的方法是摆脱你的IsFileLocked方法,并将File.AppendAllText放在try ... catch中。如果File.AppendAllText由于文件已被锁定而失败,请暂停一点,然后再次尝试File.AppendAllText。
答案 3 :(得分:0)
如评论中所述,您需要使用显示器或互斥锁,以确保您的支票确实对您有益。 Monitor版本更快,但不是过程安全(换句话说,如果用户打开程序的两个副本,它将无法保护您)。看起来如下:
try
{
Monitor.Enter(shared_variable);
if (IsFileLocked(myFile)) return;
// ... process file here ...
}
finally
{
Monitor.Exit(shared_variable);
}
在此示例中,只需将“shared_variable”声明为可在所有线程之间共享的任何对象。您甚至可以为此目的创建一个对象。使用互斥锁的替代方案看起来几乎相同但速度要慢得多:
Mutex globalWait = new Mutex();
if (globalWait.WaitOne())
{
try
{
if (IsFileLocked(myFile)) return;
// ... process file here ...
}
finally
{
globalWait.ReleaseMutex();
}
}
一个非常重要的注意事项 - 这些“WaitOne”和“Enter”例程将无限期地等待线程变为可用。如果这些线程以任何方式相互依赖,那么最终可能会出现死锁。有许多优秀的资源来处理死锁,但它需要非常清楚!
答案 4 :(得分:0)
一般来说,CanWrite
这样的样式函数很少,很少需要。想象一下这段代码:
if(CanRead(someFile))
{
x = Read(someFile);
}
else
{
// cannot read
}
这有问题。可读性可以在这两个调用之间切换(因此您的同步问题仍然存在)。当您只需要执行一次时,您还将有效地打开文件两次。这里更好的解决方案是从Read()中正确处理错误。
try
{
x = Read(someFile);
}
catch(...)
{
// cannot read.
}
更具体地说,您似乎正在编写一个跨进程工作的日志类。正如我在上面的示例中所做的那样,无论如何,都应该正确处理日志记录类中的写入错误,而不必使用CanWrite
函数。
操作系统已经“同步”文件,因此您在写作时不会遇到任何竞争条件。但是当然写入可能不是原子的,所以你最终可能会在另一个进程文本的中间插入一些文本。
您希望您的日志记录类形成具有互斥的队列。进程A获取互斥锁,执行文件所需的所有操作,并将互斥锁传递给进程B,后者轮流等等。
为此,您可以创建一个命名的互斥锁,正如@Akash Kava已经显示的那样。