C# 在读写或替换期间锁定文本文件
我有一个应用程序,需要在其中创建文件,文件名中包含一个唯一的序列号。我的第一个想法是使用(因为这个应用程序没有任何其他数据存储)一个包含数字的文本文件,我会增加这个数字,这样我的应用程序将始终创建一个具有唯一id的文件。 然后我想,当有多个用户同时提交此应用程序时,可能有一个进程在前一个进程编写txt文件之前读取该文件。因此,我正在寻找一种在同一“进程”中读取和写入文件的方法(使用try-catch,这样我就可以知道另一个进程何时正在使用该文件,然后等待并尝试从中读取其他几次),而不必在其间解锁该文件。 如果我上面所说的听起来是一个糟糕的选择,你能给我一个替代方案吗?那么,对于像我的案例这样的应用程序,您将如何跟踪唯一的标识号C# 在读写或替换期间锁定文本文件,c#,.net,file,C#,.net,File,我有一个应用程序,需要在其中创建文件,文件名中包含一个唯一的序列号。我的第一个想法是使用(因为这个应用程序没有任何其他数据存储)一个包含数字的文本文件,我会增加这个数字,这样我的应用程序将始终创建一个具有唯一id的文件。 然后我想,当有多个用户同时提交此应用程序时,可能有一个进程在前一个进程编写txt文件之前读取该文件。因此,我正在寻找一种在同一“进程”中读取和写入文件的方法(使用try-catch,这样我就可以知道另一个进程何时正在使用该文件,然后等待并尝试从中读取其他几次),而不必在其间解锁
谢谢。编辑:正如Alireza在评论中指出的,这不是在多个应用程序之间锁定的有效方法 您可以始终
锁定
对文件的访问权限(因此不需要依赖异常)。
e、 g:
Edit2:似乎可以使用互斥
执行应用程序间信令
private static System.Threading.Mutex m = new System.Threading.Mutex(false, "LockMutex");
void AccessMethod()
{
try
{
m.WaitOne();
// Access the file
}
finally
{
m.ReleaseMutex();
}
}
但这不是生成唯一ID的最佳模式。也许数据库中的序列会更好?如果您没有数据库,可以使用
Guid
s或本地数据库(我认为即使是Access也会更好)我更喜欢使用全局互斥的复杂通用解决方案。它使用一个名称前缀为“Global\”的互斥体,这使它成为系统范围内的互斥体,即在所有进程中共享一个互斥体实例。若您的程序在友好的环境中运行,或者您可以指定严格的权限限制到您可以信任的用户帐户,那个么它工作得很好
请记住,此解决方案不是事务性的,不受线程中止/进程终止的保护
非事务性意味着,如果进程/线程被捕获在存储文件修改的中间,并终止/中止,则存储文件将处于未知状态。例如,它可以保留为空。通过先写入新值,保存文件,然后删除以前的值,可以防止数据丢失(上次使用的索引丢失)。读取过程应该期望一个包含多个数字的文件,并且应该使用最大的值
未针对线程中止进行保护意味着,如果获得互斥的线程意外中止和/或您没有适当的异常处理,则互斥可以在创建该线程的进程的生命周期内保持锁定。为了使解决方案中止得到保护,您必须在获得锁时执行超时,即更换以下永远等待的行blnResult = iLock.Mutex.WaitOne();
有超时的东西
综上所述,我试图说,如果您正在寻找一个真正强大的解决方案,您将使用某种事务性数据库,或者自己编写一种这样的数据库:)
这是没有超时处理的工作代码(我的解决方案中不需要它)。首先,它足够健壮
using System;
using System.IO;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
namespace ConsoleApplication31
{
class Program
{
//You only need one instance of that Mutex for each application domain (commonly each process).
private static SMutex mclsIOLock;
static void Main(string[] args)
{
//Initialize the mutex. Here you need to know the path to the file you use to store application data.
string strEnumStorageFilePath = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
"MyAppEnumStorage.txt");
mclsIOLock = IOMutexGet(strEnumStorageFilePath);
}
//Template for the main processing routine.
public static void RequestProcess()
{
//This flag is used to protect against unwanted lock releases in case of recursive routines.
bool blnLockIsSet = false;
try
{
//Obtain the lock.
blnLockIsSet = IOLockSet(mclsIOLock);
//Read file data, update file data. Do not put much of long-running code here.
//Other processes may be waiting for the lock release.
}
finally
{
//Release the lock if it was obtained in this particular call stack frame.
IOLockRelease(mclsIOLock, blnLockIsSet);
}
//Put your long-running code here.
}
private static SMutex IOMutexGet(string iMutexNameBase)
{
SMutex clsResult = null;
clsResult = new SMutex();
string strSystemObjectName = @"Global\" + iMutexNameBase.Replace('\\', '_');
//Give permissions to all authenticated users.
SecurityIdentifier clsAuthenticatedUsers = new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null);
MutexSecurity clsMutexSecurity = new MutexSecurity();
MutexAccessRule clsMutexAccessRule = new MutexAccessRule(
clsAuthenticatedUsers,
MutexRights.FullControl,
AccessControlType.Allow);
clsMutexSecurity.AddAccessRule(clsMutexAccessRule);
//Create the mutex or open an existing one.
bool blnCreatedNew;
clsResult.Mutex = new Mutex(
false,
strSystemObjectName,
out blnCreatedNew,
clsMutexSecurity);
clsResult.IsMutexHeldByCurrentAppDomain = false;
return clsResult;
}
//Release IO lock.
private static void IOLockRelease(
SMutex iLock,
bool? iLockIsSetInCurrentStackFrame = null)
{
if (iLock != null)
{
lock (iLock)
{
if (iLock.IsMutexHeldByCurrentAppDomain &&
(!iLockIsSetInCurrentStackFrame.HasValue ||
iLockIsSetInCurrentStackFrame.Value))
{
iLock.MutexOwnerThread = null;
iLock.IsMutexHeldByCurrentAppDomain = false;
iLock.Mutex.ReleaseMutex();
}
}
}
}
//Set the IO lock.
private static bool IOLockSet(SMutex iLock)
{
bool blnResult = false;
try
{
if (iLock != null)
{
if (iLock.MutexOwnerThread != Thread.CurrentThread)
{
blnResult = iLock.Mutex.WaitOne();
iLock.IsMutexHeldByCurrentAppDomain = blnResult;
if (blnResult)
{
iLock.MutexOwnerThread = Thread.CurrentThread;
}
else
{
throw new ApplicationException("Failed to obtain the IO lock.");
}
}
}
}
catch (AbandonedMutexException iMutexAbandonedException)
{
blnResult = true;
iLock.IsMutexHeldByCurrentAppDomain = true;
iLock.MutexOwnerThread = Thread.CurrentThread;
}
return blnResult;
}
}
internal class SMutex
{
public Mutex Mutex;
public bool IsMutexHeldByCurrentAppDomain;
public Thread MutexOwnerThread;
}
}
如果是单个应用程序,则可以在应用程序设置中存储当前号码。在启动时加载该数字。然后,对于每个请求,您可以安全地增加它并使用结果。程序关闭时保存序列号。例如:
private int _fileNumber;
// at application startup
_fileNumber = LoadFileNumberFromSettings();
// to increment
public int GetNextFile()
{
return Interlocked.Increment(ref _fileNumber);
}
// at application shutdown
SaveFileNumberToSettings(_fileNumber);
或者,您可能希望确保文件号每次递增时都会保存。如果是,请更改GetNextFile
方法:
private readonly object _fileLock = new object();
public int GetNextFile()
{
lock (_fileLock)
{
int result = ++_fileNumber;
SaveFileNumbertoSettings(_fileNumber);
return result;
}
}
还请注意,使用注册表而不是文件可能是合理的。您想过SQlite或类似的持久层吗?你不需要重新发明轮子!这可能是一个选择,但我不想增加任何文件。我假设SQLLite将存储所有以前的ID。我的文本文件将只包含最新的id。我刚刚想:我可以使用你的想法,有两个字段,一个告诉我它是否被锁定,另一个带有数字。我将首先通过将字段更新为“true”来“锁定”,然后运行“读/写”事务,然后将“锁定”字段设置为false。我会试试的。你不需要增加文件大小!使用任何原子功能增加单个计数。这是处理多个并发请求的单个程序,还是同时运行的多个程序?
lock
的作用域是一个应用程序空间
。如果多个并行进程试图访问该文件,lock
无法按预期工作。哦,好吧,你教了我一些东西。问题是有很多进程实例,而不是一个。您的解决方案仅适用于单进程单线程模型。@zverev.eugene:OP对“进程”一词的使用模棱两可。在上下文中,听起来好像只有一个流程实例(应用程序),他使用术语“流程”来表示对该应用程序的单个请求。当然,我可能看错了。在任何情况下,我的答案都是基于这样一个假设,即有一个进程可以为多个并发请求提供服务。它可以轻松地处理多个线程。即使有一个进程有多个进程,您的解决方案也显示了实例变量(非静态)的使用,该变量将与定义它的类的每个实例重复。如果重新定义变量并使其成为静态变量,那么在许多服务器应用程序(如ASP.NET或WCF)的上下文中,您的解决方案仍然是错误的,因为在单个进程中有多个应用程序域,甚至静态变量也将被复制。考虑到所有这些,我不建议在没有全局系统范围同步机制的情况下使用“代码内”数据存储。
private readonly object _fileLock = new object();
public int GetNextFile()
{
lock (_fileLock)
{
int result = ++_fileNumber;
SaveFileNumbertoSettings(_fileNumber);
return result;
}
}