Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: одновременный доступ к файлу из нескольких процессов  (Прочитано 44171 раз)
0 Пользователей и 1 Гость смотрят эту тему.
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« : 02-10-2019 09:28 » 

День добрый,

прошу помощи, ибо что-то я подвисла там, где совершенно не ожидала... Я зол!

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

Т.к. задача была поставлена предельно четко - прототип с минимальными затратами времени, то в виде базы данных я использовала обычные файлы (тхт или csv - в зависимости от данных).

Пока работает 1 человек, все супер. Но! Естественно это утопия Ага Работать должны в идеале сразу несколько коллег параллельно.
И тут всплывает вопрос - как защитить доступ к файлам в момент чтения - записи.

Идея была проста  Отлично:
- есть класс Helper, где лежат различные общие для всех функции, в частности функции чтения - записи файлов.
Я решила обезопасить себя с двух сторон, посему ввела 2 мутекса - один стоит на самих функциях чтения - записи файлов и обявлен как private static, второй - общий для всех операций, включается каждый раз, когда кто-то нажимает на кнопку "сохранить" (т.о. я безопасно выполняю группу функций):

Код:
    public class Helpers
    {

        private static Mutex m_MutexForReadWriteFile;
        public static Mutex MutexForOperations;

        public Helpers()
        {
            m_MutexForReadWriteFile = CreateMutex(@"m_MutexForReadWriteFile");
            MutexForOperations = CreateMutex(@"CommonMutexForOperations_ReadWriteFile");
        }

        private Mutex CreateMutex(string mutexName)
        {
            Mutex mutexToCreate = null;
            bool mutexExists = true;
            try
            {
                mutexToCreate = Mutex.OpenExisting(mutexName);
            }
            catch (WaitHandleCannotBeOpenedException ex)
            {
                mutexExists = false;
            }

            if (!mutexExists)
                mutexToCreate = new Mutex(false, mutexName);

            return mutexToCreate;
        }

        ***************   Чтение - запись файлов  - начало**************************************

        private FileStream CreateFileStreamForReadWriteFile(string wholeFileName, FileMode mode, FileAccess access)
        {
            return new FileStream(wholeFileName, mode, access, FileShare.ReadWrite);
        }

        public Dictionary<int, string> ReadTextFile(string fileName, out Int64 updateCounter, string path = "")
        {
            Dictionary<int, string> fileContent = null;
            m_MutexForReadWriteFile.WaitOne();
            {
                 using (FileStream fileStreamTxt = CreateFileStreamForReadWriteFile(wholeFileName, FileMode.Open, FileAccess.Read))
                {
                    using (System.IO.StreamReader txtFileToRead =
                        new System.IO.StreamReader(fileStreamTxt))
                    {
                        while ((oneLine = txtFileToRead.ReadLine()) != null)
                        {
                             // тут читаем / сохраняем...
                         }

                        }
                        txtFileToRead.Close();
                    }
                }
            }
            m_MutexForReadWriteFile.ReleaseMutex();

            return fileContent;
        }

        public void WriteInfosInFile(string fileName, StringBuilder allInfosToSaveInFile, bool appendNewItemInExistingFile = false, string path = "" )
        {
            m_MutexForReadWriteFile.WaitOne();
            {
                using (FileStream fileStream = CreateFileStreamForReadWriteFile(wholeFileName, FileMode.Create, FileAccess.Write))
                {
                    using (StreamWriter fileToWrite = new StreamWriter(fileStream))
                    {
                        fileToWrite.Write(allInfosToSaveInFile.ToString());
                        fileToWrite.Close();
                    }
                }
 
            }
            m_MutexForReadWriteFile.ReleaseMutex();
        }

        ***************   Чтение - запись файлов  - конец**************************************

        ***************   выполнение группы функций при нажатии на кнопку "сохранить"  - начало**************************************


        public static bool MutexStartWaiting()
        {
            bool isMutexFree = false;
            try
            {
                isMutexFree = MutexForOperations.WaitOne(1000);
            }
            catch(AbandonedMutexException ex)
            {
                MutexRelease();
                isMutexFree = MutexForOperations.WaitOne(1000);
            }

            return isMutexFree;
        }

        public static void MutexRelease()
        {
            MutexForOperations.ReleaseMutex();
        }

        // основная функция - ее вызывают все при нажатии на кнопку "сохранить"
        public void SaveChangesSafetyBetweenApplications(ISaveChangesWithMutexSupport currentObjectToNeedSaving,
                                                            ArrayList infoToSave)
        {
            MutexStartWaiting();

            currentObjectToNeedSaving.SaveChangesSafety(infoToSave);

            MutexRelease();
        }


        ***************   выполнение группы функций при нажатии на кнопку "сохранить"  - конец**************************************

}

Любой класс, вызывающий процесс сохранения информации выглядит где-то так:

Код:
    class AddOrChangeColor : AddOrChangeDataBase, ISaveChangesWithMutexSupport
    {
        protected Helpers m_Helper; // обявлена и создана в базовом классе

        public override void SaveData()
        {
            string oldColor = m_DataContainerWithAllInfosAboutComponents.AllExistingComponentColors[m_IndexOfModifiedColorInList];

            // тут я вызываю основную функцию,  использующую глобальный мутекс
            m_Helper.SaveChangesSafetyBetweenApplications(this, new ArrayList() { oldColor });
        }

        // функция, вызываемая после установления глобального мутекса (принадлежит интерфейсу ISaveChangesWithMutexSupport)
        // в связи с тем, что данные хранятся в файлах и возможны обновления, сначала я считываю текущее состояние файла с данными и перезаписываю контейнер, используя свежие данные (при этом я надеюсь, что никто другой в этот момент ничего в этот файл больше записать не может).
Потом проверяю, не сделал ли кто-то другой уже такие же изменения. Если нет - спокойно вношу свои изменения и перезаписываю файл. После чего освобождаю мутекс
        public void SaveChangesSafety(ArrayList infoToSave)
        {
            m_Helper.RefreshDataFromConfigFileIfNecessary(m_DataContainerWithAllInfosAboutComponents,
                DataContainer.UpdateCountersIndexies.AllExistingComponentColors,
                Constants.c_Filename_Config_Colors, "",
                m_DataContainerWithAllInfosAboutComponents.ReadColorsFromFile);

            if (m_SaveDataIsCurrentOperation)
                SaveChangesSafety_SaveDataIsCurrentOperation(infoToSave);
            else
                SaveChangesSafety_DeleteDataIsCurrentOperation(infoToSave);
        }
   }

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

Начала с дебагом проверять создание мутексов и нашла проблему - каждая инстанция приложения создает свои мутексы! хотя после старта первой инстанции они уже существуют!

Вопрос - и где я идиот?...   Быть такого не может
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #1 : 02-10-2019 11:32 » 

sqllite
Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #2 : 02-10-2019 11:45 » 

Славик,

пока надо оставаться на файлах.
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #3 : 02-10-2019 16:15 » 

У тебя два разных компа со своим окружением. Они между собой никак не связаны. Наверно кроме расшаренной папки. Следовательно создание мьютекса на одной машине никоим образом не должно производить воздействие на вторую машину. IMHO в контексте данного тех задания никак не обойтись без сервиса крутяшегося на одной машине и обслуживаюшего все остальные машины. Он собственно будет единолично обслуживать эти файлы. А уже на другие машины иницилизировать клиентскую часть.
Ну или не искать приключений, а сразу делать на базах данных. Пусть и простейший концепт.  
Третий вариант: открывать файлы только на дополнение с выставлением метки однотипности. При чтении данного файла, считать что последняя запись самая актуальная. Чтобы файлы не слишком распухали, должен крутится отдельный сервис, который бы убирал все устаревшие данные.
« Последнее редактирование: 02-10-2019 16:20 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #4 : 02-10-2019 20:59 » 

Finch,

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

Цитата
никак не обойтись без сервиса крутяшегося на одной машине и обслуживаюшего все остальные машины

а что мне даст в таком случае сервис? что он должен делать?
Прости за глупые вопросы - я с этим не сталкивалась.
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #5 : 02-10-2019 21:17 » 

Не важно где находится exe. Он запускается на конкретной машине с конкретным окружением. Если бы ты запускала бы его на той же самой машине, тогда бы да, мьютексы были бы видны всем процессам на данной машине. Но при запуске на разных машинах у них разное окружение системы.
Ну вариант с сервисом, тот же самый, что в принципе делает SQL сервер. Клиентская часть ничего не знает о внутренем устройстве файлов. Клиент делает запрос на сервер на получение данных или на их запись. А уже сервер сам производит изменения. Ну естественно разрешает конфликты. Если пришли несколько запросов на запись.
« Последнее редактирование: 02-10-2019 21:21 от Finch » Записан

Не будите спашяго дракона.
             Джаффар (Коша)
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #6 : 02-10-2019 21:33 » 

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

Не будите спашяго дракона.
             Джаффар (Коша)
RXL
Технический
Администратор

ru
Offline Offline
Пол: Мужской

WWW
« Ответ #7 : 02-10-2019 22:21 » 

Можно попробовать блокировки файлов. Но чревато для сетевой ФС.
Записан

... мы преодолеваем эту трудность без синтеза распределенных прототипов. (с) Жуков М.С.
Алексей++
глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #8 : 03-10-2019 03:16 » 

задача была поставлена предельно четко - прототип с минимальными затратами времени

Ирина, объясни работодателям, что минимальные затраты в данном случае - использовать любую СУБД. Все остальные варианты будут не минимальными затратами - велосипедами и граблями (плюс ещё учитывай, что на СУБД с этих отдельных файлов всё равно потом надо будет переносить всё - двойная работа)
Записан

Sla
Команда клуба

ua
Offline Offline
Пол: Мужской

WWW
« Ответ #9 : 03-10-2019 06:36 » 

Вот все верно говорят
На сетевой машине поднимаешь сервис который слушает запросы
Чтение и запись, это не обязательно sql сервер
Владелец файла - только один - сервис.

Если файл отредактирован, то он должен записаться, и дать широкоформатное сообщение - файл изменен.

Твоя софтинка, должна уметь слушать это сообщение.

Записан

Мы все учились понемногу... Чему-нибудь и как-нибудь.
darkelf
Молодой специалист

de
Offline Offline

« Ответ #10 : 04-10-2019 09:24 » 

К сожалению не могу подсказать, где это в .Net, но в WinAPI при создании/открытии файла функцией CreateFile можно указать режим "разделения" (FILE_SHARE_*) кроме этого, можно блокировать часть файла при помощи LockFile(). Возможно как-то поможет пример.
Записан
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #11 : 04-10-2019 11:26 » 

всем огромное спасибо!
Попробую посмотреть в указанных направлениях. Скорее всего, потом все равно приду с вопросами Ага
Но в общем и целом мне все это не очень нравится... "быстро" обычно выглядит по-другому... я сильно просчиталась
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Finch
Спокойный
Администратор

il
Offline Offline
Пол: Мужской
Пролетал мимо


« Ответ #12 : 04-10-2019 13:20 » 

darkelf, Нужно конечно эксперементировать. Но на какой стороне будет действовать данный флаг? Со стороны сервера, на котором физически лежит данный файл, или на стороне клиента? Если второй случай, тогда это таже история, что и мьютексы.
Записан

Не будите спашяго дракона.
             Джаффар (Коша)
darkelf
Молодой специалист

de
Offline Offline

« Ответ #13 : 04-10-2019 14:46 » 

По логике, по крайней мере как это по внешним признакам отрабатывается в Windows, там этот флаг находится на стороне сервера - там, где этот файл располагается физически. Но пробовать действительно надо.

PS: про блокирование файлов C# нашел здесь, а оттуда послали в документацию.
Записан
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #14 : 05-10-2019 05:31 » 

посетила светлая мысль - а что если перед началом серии операций вместо мутекса тупо устанавливать файловый доступ как readonly и до конца его не менять? а остальные проходят и проверяют - если файл только для чтения - ждем-с..

darkelf, спасибо за ссылку - я потом спокойно почитаю!
Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Джон
просто
Администратор

de
Offline Offline
Пол: Мужской

« Ответ #15 : 08-10-2019 00:23 » 

Ир, ну это собственно то, на что тебе уже давно намякивают. Первая инстанция, заполучившая файл, открывает его с экслюзивными правами, а остальные "ждут". Только! не разрешай пользователю редактировать данные ДО того как откроешь и прочитаешь файл -> merge тот ещё геморрой. Тем более txt csv
Записан

Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома.
"Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash
"Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman
"All science is either physics or stamp collecting." Ernest Rutherford
"Wer will, findet Wege, wer nicht will, findet Gründe."
Ochkarik
Команда клуба

ru
Offline Offline
Пол: Мужской

« Ответ #16 : 08-10-2019 11:01 » 

Finch, darkelf, касательно LockFileEx function:
Цитата
In Windows 8 and Windows Server 2012, this function is supported by the following technologies.
Technology   Supported
Server Message Block (SMB) 3.0 protocol   Yes
SMB 3.0 Transparent Failover (TFO)           Yes
SMB 3.0 with Scale-out File Shares (SO)   Yes
Cluster Shared Volume File System (CsvFS)   Yes
Resilient File System (ReFS)                   Yes
Записан

RTFM уже хоть раз наконец!  RTFM :[ ну или хотя бы STFW...
Malaja
Команда клуба

de
Offline Offline
Пол: Женский

« Ответ #17 : 31-10-2019 12:36 » 

Какая же это попа - организация мультиюзера... Я зол!

Я продолжаю ловить баги, что вполне естественно - вся система сначала была рассчитана на 1 юзера, идея играть на общем рояле в 10 рук из разных комнат появилась у начальницы потом, когда она увидела, что этот тул может...  Не могу...

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

Закончилось все тем, что:
- был создан пустой текстовый файл, который выступает в роли той собаки, которая лает (= мутекса)
- на входе в функции чтения / записи этот файл открывается на чтение в FileStream  и таким образом блокирует всех остальных претендентов на вызов этой функции. Далее - выполняется чтение / запись файла с данными с помощью StreamReader / StreamWriter , после окончания чего я закрываю поток, который держит доступ к файлу - мутексу:
Код: (C#)
        public Dictionary<int, string> ReadTextFile(string fileName, out Int64 updateCounter, string path = "")
        {
            FileStream fileStreamMutex = null;
            LockFilesBeforeReadWriteOperations(ref fileStreamMutex);

            using (System.IO.StreamReader txtFileToRead =
                new StreamReader(wholeFileName)
                //new System.IO.StreamReader(fileStreamTxt)
                )
            {
                while ((oneLine = txtFileToRead.ReadLine()) != null)
                {
                    // сохраняем прочитанное
                }
                txtFileToRead.Close();
            }

            UnlockFilesAfterReadWriteOperations(ref fileStreamMutex);

            return fileContent;
        }

        void LockFilesBeforeReadWriteOperations(ref FileStream fileStreamMutex)
        {
            LockFiles(GetMutexFileNameForReadWrite(), ref fileStreamMutex);// имя файла сохранено как константа
        }

        void UnlockFilesAfterReadWriteOperations(ref FileStream fileStreamMutex)
        {
            UnlockFiles(ref fileStreamMutex);
        }


        void LockFiles(string wholeFileName, ref FileStream fileStreamMutex, string warningMessage = "")
        {
            fileStreamMutex = CreateFileStreamForReadWriteFile(wholeFileName, FileMode.Open, FileAccess.Read,
                warningMessage);
        }

        void UnlockFiles(ref FileStream fileStreamMutex)
        {
            fileStreamMutex.Close();
            fileStreamMutex.Dispose();
        }

        private FileStream CreateFileStreamForReadWriteFile(string wholeFileName, FileMode mode, FileAccess access
            , string warningMessage = "")
        {
            FileStream fileStreamMutex = null;
            bool waitTillFileWillBeFree = true;
            bool firstCall = true;
            int lockCounter = 0;
            while (waitTillFileWillBeFree && fileStreamMutex == null)
            {
                fileStreamMutex = TryToCreateFileStreamForReadWriteFile(wholeFileName, mode, access,
                                ref waitTillFileWillBeFree, ref lockCounter, warningMessage);
                if (firstCall)
                    firstCall = false;

                if (waitTillFileWillBeFree && !firstCall && !warningMessage.Equals(String.Empty))
                    warningMessage = String.Empty;

                if (waitTillFileWillBeFree && fileStreamMutex == null)
                    Thread.Sleep(100);

            }

            return fileStreamMutex;
        }

        private FileStream TryToCreateFileStreamForReadWriteFile(string wholeFileName, FileMode mode, FileAccess access,
            ref bool waitTillFileWillBeFree, ref int lockCounter, string warningMessage = "")
        {
            waitTillFileWillBeFree = true;
            FileShare share = FileShare.None;

            if (lockCounter > 1000)
                System.GC.Collect();
           
            try
            {
                return new FileStream(wholeFileName, mode, access, share);
            }
            catch (IOException ex)
            {
                lockCounter++;
                if (!warningMessage.Equals(String.Empty))
                {
                    DialogResult result = ShowMessageBox_Warning_WithDlgResult(warningMessage);
                    if (result == DialogResult.No)
                    {
                        waitTillFileWillBeFree = false;
                        return null;
                    }
                }
            }

            return null;
        }
Проблема, которую я так пока и не решила - как снять блокировку этого мутекс-файла. Принудительный вызов System.GC.Collect() похоже не особо помогает... Делать переменную типа FileStream для этого файла переменной класса опасно тоже..

- аналогично решила вопрос с обработкой нескольких записей одновременно (например - изменилось имя компоненты, что влечет за собой сначала чтение нескольких файлов из базы данных на предмет определения обновлений, сделанных к этому моменту, потом сведение всех изменений воедино и запись или прерывание изменений данного пользователя).
Т.е. был создан еще один пустой файл типа мутекс, он открывается в потоке на чтение, потом выполняется группа операций, причем в этот момент никто ничего не может никуда записать, т.о. я могу гарантировать сохранность данных в момент анализа. После окончания группы операций мутексный файл закрывается.
« Последнее редактирование: 01-11-2019 00:10 от Джон » Записан

холоднокровней, Маня, Ви не на работе
---------------------------------------
четкое определение сущности бытия:
- А мы в прошлом или в будущем?- спросила Алиса.
- Мы в жопе, - ответил кролик.
- А "жопа" - это настоящее? - спросила Алиса.
- А "жопа" - это у нас символ вечности.
Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines