Мое веб-приложение возвращает файл из файловой системы. Эти файлы являются динамичными, таким образом, у меня нет способа знать имена 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);
}
}
}
Но мне не нравится то, что я должен поймать исключения, пробуйте несколько раз и ожидайте неточное количество времени :|
Вы можете справиться с этим, используя аргумент FileMode.CreateNew для конструктора потока. Один из потоков проиграет и обнаружит, что файл уже был создан на микросекунду ранее другим потоком. И получит исключение IOException.
Затем ему нужно будет вращаться, ожидая полного создания файла. Который вы применяете с помощью FileShare.None. Ловля исключений здесь не имеет значения, все равно крутится. В любом случае для этого нет другого обходного пути, если вы не P / Invoke.
Ваш вопрос действительно заставил меня задуматься.
Вместо того, чтобы каждый поток отвечал за доступ к файлам и блокировал их, что, если бы вы использовали очередь файлов, которые необходимо сохранить, и чтобы один фоновый рабочий поток удалялся из очереди и сохранялся?
Пока фоновый рабочий запускает прочь, вы можете заставить потоки веб-приложения возвращать значения db до тех пор, пока файл действительно не существует.
Я опубликовал очень простой пример этого на GitHub .
Не стесняйтесь дать ему шанс и дайте мне знать, что вы думаете.
К вашему сведению, если у вас нет git, вы можете использовать svn, чтобы вытащить его http://svn.github.com/statianzo/MultiThreadFileAccessWebApp
У вас есть способ определить, какие файлы создаются?
Допустим, каждый из этих файлов соответствует уникальному идентификатору в вашей базе данных. Вы создаете централизованное хранилище (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, конечно, не идеальна, но если вашему приложению требуется глобальная синхронизация, то это способ ее добиться.
Я думаю, что правильный подход будет следующим: создать набор строк, где 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
}
Почему вы просто не используете базу данных - например, если у вас есть способ связать имя файла с данными из базы данных, которую он содержит, просто добавьте некоторую информацию в базу данных, которая указывает, существует ли файл с этой информацией в настоящее время, и когда он был создан, насколько устарела информация в файле и т. д. Когда потоку нужна некоторая информация, он проверяет базу данных, чтобы увидеть, существует ли этот файл, а если нет, он записывает строку в таблицу, говоря, что он создает файл. Когда это будет сделано, он обновит эту строку логическим значением, сообщающим, что файл готов к использованию другими.
Хорошая вещь в этом подходе - вся ваша информация находится в одном месте - так что вы можете хорошо исправить ошибки - например, если поток, создающий файл, по какой-то причине умирает, другой поток может прийти и решить переписать файл, потому что время создания слишком старое. Вы также можете создавать простые процессы пакетной очистки и получать точные данные о том, как часто определенные данные используются для файла, как часто информация обновляется (просматривая время создания и т. Д.). Кроме того, вам не придется выполнять множество операций по поиску дисков в файловой системе, поскольку разные потоки ищут разные файлы повсюду, особенно если вы решите использовать несколько интерфейсных машин для поиска на общем диске.
Сложная вещь - вам нужно убедиться, что ваша база данных поддерживает блокировку на уровне строк в таблице, в которую потоки записывают данные при создании файлов, потому что в противном случае сама таблица может быть заблокирована, что может сделать это неприемлемо медленным.