Как Заблокировать файл и избежать чтений, в то время как он пишет

Мое веб-приложение возвращает файл из файловой системы. Эти файлы являются динамичными, таким образом, у меня нет способа знать имена o, сколько из них там будет. Когда этот файл не существует, приложение создает его из базы данных. Я хочу избежать, чтобы два различных потока воссоздали тот же файл одновременно, или что попытка потока возвратить файл, в то время как другой поток создает его.

Кроме того, я не хочу получать блокировку по элементу, который характерен для всех файлов. Поэтому я должен заблокировать файл как раз в то самое время, когда я создаю его.

Таким образом, я хочу заблокировать файл, пока его воссоздание не завершено, если другая попытка потока получить доступ к нему... он должен будет ожидать файл быть разблокированным.

Я читал о FileStream. Блокировка, но я должен знать длину файла, и она не предотвратит ту другую попытку потока считать файл, таким образом, она не будет работать на мой особый случай.

Я читал также о FileShare. Ни один, но это выдаст исключение (который тип исключительной ситуации?), если другая попытка потока/процесса получить доступ к файлу..., таким образом, я должен разработать, "попробовала еще раз, в то время как дает сбой", потому что я хотел бы избежать поколения исключения... и мне не нравится слишком много что подход, хотя, возможно, нет лучшего пути.

Подход с FileShare. Ни один не был бы этим более или менее:

    static void Main(string[] args)
    {
        new Thread(new ThreadStart(WriteFile)).Start();
        Thread.Sleep(1000);
        new Thread(new ThreadStart(ReadFile)).Start();

        Console.ReadKey(true);
    }

    static void WriteFile()
    {
        using (FileStream fs = new FileStream("lala.txt", FileMode.Create, FileAccess.Write, FileShare.None))
        using (StreamWriter sw = new StreamWriter(fs))
        {
            Thread.Sleep(3000);
            sw.WriteLine("trolololoooooooooo lolololo");
        }
    }

    static void ReadFile()
    {
        Boolean readed = false;
        Int32 maxTries = 5;

        while (!readed && maxTries > 0)
        {
            try
            {
                Console.WriteLine("Reading...");
                using (FileStream fs = new FileStream("lala.txt", FileMode.Open, FileAccess.Read, FileShare.Read))
                using (StreamReader sr = new StreamReader(fs))
                {
                    while (!sr.EndOfStream)
                        Console.WriteLine(sr.ReadToEnd());
                }
                readed = true;
                Console.WriteLine("Readed");
            }
            catch (IOException)
            {
                Console.WriteLine("Fail: " + maxTries.ToString());
                maxTries--;
                Thread.Sleep(1000);
            }
        }
    }

Но мне не нравится то, что я должен поймать исключения, пробуйте несколько раз и ожидайте неточное количество времени :|

8
задан Benjamin 4 February 2014 в 11:41
поделиться

5 ответов

Вы можете справиться с этим, используя аргумент FileMode.CreateNew для конструктора потока. Один из потоков проиграет и обнаружит, что файл уже был создан на микросекунду ранее другим потоком. И получит исключение IOException.

Затем ему нужно будет вращаться, ожидая полного создания файла. Который вы применяете с помощью FileShare.None. Ловля исключений здесь не имеет значения, все равно крутится. В любом случае для этого нет другого обходного пути, если вы не P / Invoke.

3
ответ дан 5 December 2019 в 22:17
поделиться

Ваш вопрос действительно заставил меня задуматься.

Вместо того, чтобы каждый поток отвечал за доступ к файлам и блокировал их, что, если бы вы использовали очередь файлов, которые необходимо сохранить, и чтобы один фоновый рабочий поток удалялся из очереди и сохранялся?

Пока фоновый рабочий запускает прочь, вы можете заставить потоки веб-приложения возвращать значения db до тех пор, пока файл действительно не существует.

Я опубликовал очень простой пример этого на GitHub .

Не стесняйтесь дать ему шанс и дайте мне знать, что вы думаете.

К вашему сведению, если у вас нет git, вы можете использовать svn, чтобы вытащить его http://svn.github.com/statianzo/MultiThreadFileAccessWebApp

1
ответ дан 5 December 2019 в 22:17
поделиться

У вас есть способ определить, какие файлы создаются?

Допустим, каждый из этих файлов соответствует уникальному идентификатору в вашей базе данных. Вы создаете централизованное хранилище (Singleton?), Где эти идентификаторы могут быть связаны с чем-то запираемым (Dictionary). Поток, которому необходимо читать / писать в один из этих файлов, делает следующее:

//Request access
ReaderWriterLockSlim fileLock = null;
bool needCreate = false;
lock(Coordination.Instance)
{
    if(Coordination.Instance.ContainsKey(theId))
    {
        fileLock = Coordination.Instance[theId];
    }
    else if(!fileExists(theId)) //check if the file exists at this moment
    {
        Coordination.Instance[theId] = fileLock = new ReaderWriterLockSlim();
        fileLock.EnterWriteLock(); //give no other thread the chance to get into write mode
        needCreate = true;
    }
    else
    {
        //The file exists, and whoever created it, is done with writing. No need to synchronize in this case.
    }
}

if(needCreate)
{
    createFile(theId); //Writes the file from the database
    lock(Coordination.Instance)
        Coordination.Instance.Remove[theId];
    fileLock.ExitWriteLock();
    fileLock = null;
}

if(fileLock != null)
    fileLock.EnterReadLock();

//read your data from the file

if(fileLock != null)
   fileLock.ExitReadLock();

Конечно, потоки, которые не следуют этому точному протоколу блокировки, будут иметь доступ к файлу.

Блокировка объекта Singleton, конечно, не идеальна, но если вашему приложению требуется глобальная синхронизация, то это способ ее добиться.

1
ответ дан 5 December 2019 в 22:17
поделиться

Я думаю, что правильный подход будет следующим: создать набор строк, где u сохранит текущее имя файла , чтобы один поток обрабатывал файл за раз, примерно так

//somewhere on your code or put on a singleton
static  System.Collections.Generic.HashSet<String> filesAlreadyProcessed= new  System.Collections.Generic.HashSet<String>();


//thread main method code
bool filealreadyprocessed = false
lock(filesAlreadyProcessed){
  if(set.Contains(filename)){
    filealreadyprocessed= true;
  }
  else{
     set.Add(filename)
  }
}
if(!filealreadyprocessed){
//ProcessFile
}
1
ответ дан 5 December 2019 в 22:17
поделиться

Почему вы просто не используете базу данных - например, если у вас есть способ связать имя файла с данными из базы данных, которую он содержит, просто добавьте некоторую информацию в базу данных, которая указывает, существует ли файл с этой информацией в настоящее время, и когда он был создан, насколько устарела информация в файле и т. д. Когда потоку нужна некоторая информация, он проверяет базу данных, чтобы увидеть, существует ли этот файл, а если нет, он записывает строку в таблицу, говоря, что он создает файл. Когда это будет сделано, он обновит эту строку логическим значением, сообщающим, что файл готов к использованию другими.

Хорошая вещь в этом подходе - вся ваша информация находится в одном месте - так что вы можете хорошо исправить ошибки - например, если поток, создающий файл, по какой-то причине умирает, другой поток может прийти и решить переписать файл, потому что время создания слишком старое. Вы также можете создавать простые процессы пакетной очистки и получать точные данные о том, как часто определенные данные используются для файла, как часто информация обновляется (просматривая время создания и т. Д.). Кроме того, вам не придется выполнять множество операций по поиску дисков в файловой системе, поскольку разные потоки ищут разные файлы повсюду, особенно если вы решите использовать несколько интерфейсных машин для поиска на общем диске.

Сложная вещь - вам нужно убедиться, что ваша база данных поддерживает блокировку на уровне строк в таблице, в которую потоки записывают данные при создании файлов, потому что в противном случае сама таблица может быть заблокирована, что может сделать это неприемлемо медленным.

0
ответ дан 5 December 2019 в 22:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: