如何不允许不同用户在同一时刻运行脚本的某些部分?

huangapple go评论71阅读模式
英文:

How not to allow running some parts of a script by different users at the exact moment of time?

问题

大家好!

我为我的公司做了一个小项目,我使用C#。我有一个用于项目的脚本。但在这之前的某一天,我和我的同事们有一个想法,即脚本将会被用户依次使用。例如,如果有用户A和用户B,可以有用户B运行脚本,然后才能让用户A运行脚本。

今天决定让用户自由地运行脚本,不再需要预定的顺序。现在我有一些想法。以下是脚本的一部分:

if (Directory.Exists(@"H:\" + doc_number + @"\detached") == false)
{
    Directory.CreateDirectory(@"H:\" + doc_number + @"\detached");
    File.WriteAllBytes(@"H:\" + doc_number + @"\detached.cms", signature_bytes);
}
else
{
    string[] files = Directory.GetFiles(@"H:\" + doc_number + @"\detached"); 
    int files_number = files.Length;
    File.WriteAllBytes(@"H:\" + doc_number + @"\detached\" + Convert.ToString(files_number + 1) + ".cms", signature_bytes);
}

首先,这里检查了目录是否存在。如果不存在,将创建目录并将第一个文件添加到其中。否则,我们只需计算目录中文件的数量,然后创建一个新文件,其名称是文件夹中文件的数量加一。

然而,我在考虑这样一种情况:用户A和用户B在这部分脚本的开始时同时运行,并且对于两者来说条件都是正面的,所以脚本执行不正确。或者如果其中一个用户开始运行这部分脚本,但他或她的PC性能较差,因此在创建目录时另一个用户会通过条件,计算文件并开始在第一个用户之前创建一个文件,这也是不正确的。

我不知道这些情况中哪一种可能性更大。如果可能发生,我该如何解决呢?

英文:

everyone!

I do a small project for my company and I use C#. I have a script for my project. But before this day, my colleagues and I had an idea that the script would be used by users one by one. For example, if there are a user A and user B, there can be the order where the user B runs the script and only then the user A can run the script.

Today the decision was made to give the users the possibility to run the script freely without the predetermined order. And now I have some thoughts. Here the part of the script:

        if (Directory.Exists(@"H:\" + doc_number + @"\detached") == false)
        {
            Directory.CreateDirectory(@"H:\" + doc_number + @"\detached");
            File.WriteAllBytes(@"H:\" + doc_number + @"\detached.cms", signature_bytes);
        }
        else
        {
            string[] files = Directory.GetFiles(@"H:\" + doc_number + @"\detached"); int files_number = files.Length;
            File.WriteAllBytes(@"H:\" + doc_number + @"\detached\" + Convert.ToString(files_number + 1) + ".cms", signature_bytes);
        }

Firstly, there is a check of the existence of a directory. If it doesn't exist, the directory will be created and the first file will be added there. Otherwise, we just count the number of files in the directory and then create a new file with a name which is the number of the files in the folder plus one.

However, I'm thinking about the situation when the user A and the user B were at the beginning of this part of the script at the same time and the condition for both would be positive so it wouldn't be executed correctly. Or if one of them started running this part earlier but his or her PC was less powerful so while creating the directory another user would go through the condition, counting files and start creating a file before the first user which would be also incorrect.

I don't know how likely one of these situations are. if so, how can I solve it?

答案1

得分: 1

你确实可能会遇到并发问题。您是正确的,不能仅依赖目录的存在来决定在if语句中采取哪个分支,因为您可能会按以下顺序执行操作:

用户A:检查目录。不存在。
用户B:检查目录。不存在。
用户A:创建目录,进入if分支。
用户B:创建目录,进入if分支。

如果代码在一台机器上的一个进程中运行,但在多个线程中运行,您可以使用一个lock语句。

如果代码在同一台机器上的不同进程中运行,您可以使用跨进程协调方法,例如Mutex

问题暗示着代码在不同的计算机上运行,但访问同一个文件系统。在这种情况下,锁文件是协调对共享资源的访问的常见机制。在这种方法中,您会尝试创建一个文件并锁定它。如果该文件已经存在并且被另一个进程锁定,您就知道有其他人先到了那里。根据您的需求,常见的情况是等待文件上的锁消失,然后获取锁并继续执行。

这种策略对上面的另外两种情况也适用,尽管效率较低。

有关如何创建带锁的文件的信息,请参阅

https://stackoverflow.com/questions/5522232/how-to-lock-a-file-with-c

英文:

Indeed, you can run into concurrency issues. And you are correct that you can't rely on the existence of a directory to decide what branch to take in your if statement because you might have operations execute in this order:

User A: Checks for directory. Does not exist. 
User B: Checks for directory. Does not exist. 
User A: Creates directory, enters if branch. 
User B: Creates directory, enters if branch.

If the code was running in one process on one machine but in multiple threads, you could use a lock statement.

If the code was running on different processes on the same machine, you could use a cross-process coordination method such as a Mutex.

The question implies that the code runs on different computers but accesses the same file system. In this case, a lock file is a common mechanism to coordinate access to a shared resource. In this approach, you would attempt to create a file and lock it. If that file already exists and is locked by another process, you know someone else got there first. Depending on your needs, a common scenario is to wait for the lock on the file to go away then acquire the lock yourself and continue.

This strategy also works for the other 2 cases above, though is less efficient.

For information about how to create a file with a lock, see

https://stackoverflow.com/questions/5522232/how-to-lock-a-file-with-c

答案2

得分: 1

你的代码存在一些问题。例如,如果文件被删除会发生什么?目录中的文件数量将与最后一个文件的数量不同,这可能导致尝试写入一个已经存在的文件。此外,请使用 Path.Combine 来创建路径,这更安全。您也不需要检查目录是否存在,因为如果目录已经存在,Directory.Create 将不执行任何操作。

以下是一些通用的解决方案:

string baseDir = Path.Combine("H:", doc_number, "detached");
Directory.Create(baseDir);

如果您只想让任意数量的用户在同一目录中创建文件,有一些更安全的解决方案:

  • 使用 GUID:
var guid = Guid.NewGuid();
var file = Path.Combine(baseDir, $"{guid}.cms");
File.WriteAllBytes(file, signature_bytes);
  • 迭代,尝试创建新文件:
bool created = false;
int index = 1;
while (!created)
{
    //首先检查文件是否存在,并获取下一个可用的索引
    var file = Path.Combine(baseDir, $"{index}.cms");
    while (File.Exists(file))
    {
        file = Path.Combine(baseDir, $"{++index}.cms");
    }
    //处理竞争条件,如果文件在我们检查后被创建
    try
    {
        //尝试创建文件,不允许其他人在文件打开时访问它
        using var stream = File.Open(file, FileMode.CreateNew, FileAccess.Write, FileShare.None);
        stream.Write(signature_bytes);
        created = true;
    }
    catch (IOException) //如果文件已经存在,尝试下一个索引
    {
        ++index;
    }
}
英文:

There are some issues with your code. For example, what would happen if a file is deleted? The number of files in the directory would be different than the number of the last file, and you can end up trying to write a file that already exists. Also, please use Path.Combine to create paths, it is safer. You also don't need to check if the directory exists, since Directory.Create will do nothing if it already exists.

Common for all solutions bellow:

string baseDir = Path.Combine("H:",doc_number, "detached");
Directory.Create(baseDir);

If you just want any number of users to create files in the same directory, some solutions that are more safe:

  • Use a GUID:
var guid = Guid.NewGuid();
var file = Path.Combine(baseDir, $"{guid}.cms");
File.WriteAllBytes(file, signature_bytes);
  • Iterate, trying to create a new file:
bool created = false;
int index = 1;
while(!created)
{
    //Check first if the file exists, and gets the next available index
    var file = Path.Combine(baseDir, $"{index}.cms");
    while(File.Exists(file))
    {
         file = Path.Combine(baseDir, $"{++index}.cms");
    }
    //Handle race conditions, if the file was created after we checked
    try
    {        
        //Try create the file, not allowing others to acess it while open
        using var stream = File.Open(file,FileMode.CreateNew,FileAccess.Write,FileShare.None);
        stream.Write(signature_bytes);
        created = true;
    }
    catch (IOException) //If the file already exists, try the next index
    {
        ++index;
    }
}

huangapple
  • 本文由 发表于 2023年2月18日 00:44:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/75487040.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定