Замена содержимого файла без прерывания доступа читателей-клиентов

#c# #concurrency #filesystems #writer

#c# #параллелизм #файловые системы #запись

Вопрос:

Существует служба, которая постоянно записывает новое содержимое в файл:

 using (var stream = File.Create(FileName))     // overwrites the file
{
    stream.Write(data, 0, data.Length);
}
  

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

Любые методы, подобные этому:

 using (var stream = File.Create(FileName   ".tmp"))
{
    stream.Write(data, 0, data.Length);
}

File.Delete(FileName);
File.Move(FileName   ".tmp", FileName);
  

может привести к отсутствию содержимого на веб-странице (с некоторой вероятностью). И служба иногда выдает IOException исключение с сообщением «Процесс не может получить доступ к файлу, поскольку он используется другим процессом».

Вопрос в следующем: как можно постоянно заменять содержимое файла без прерывания доступа клиентов readers?

Комментарии:

1. Я совершенно уверен, что это невозможно.

2. Синхронизируя доступ для чтения к файлу с глобальным мьютексом?

3. @Tergiver: К сожалению, нет контроля над кодом клиентов readers.

4. как в вашем коде Reader размещен IIS, сервис, настольное приложение или что-то еще?

Ответ №1:

В IIS вы могли бы адаптировать этот модуль (полное раскрытие, я его написал) для внедрения синхронизации в запросы на чтение. Вы могли бы сделать это, создав подкласс InterceptingHandler и добавив код типа:

 SychronizingHandler : InterceptingHandler
{
    // ...

    Semaphore mySemaphore;

    protected override bool PreFilter(System.Web.HttpContext context)
    {
        context.RewritePath("myFilePath");
        if( mySemaphore == null)
        {
            bool created;
            mySemaphore = new Semaphore(100, 0, "semphoreName", out created);
        }

        if( mySemaphore != null)
        {
            mySemaphore.WaitOne();
        }
        reutrn true;
    }

    // note this function isn't in the base class
    // you would need to add it  and call it right after the call to
    // innerHandler.ProcessRequest
    protected override void PostFilter(System.Web.HttpContext context) 
    {
        mySemaphore.Release();
        return;
    }

    protected virtual void OnError(HttpContext context, Exception except)
    {
        mySemaphore.Release();
        return base.OnError(context, except);
    }
  

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

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

Комментарии:

1. Это может сработать, если настольное приложение может читать непосредственно из IIS. В качестве альтернативы вы могли бы использовать дескриптор, обслуживающий файл A во время записи файла B, и обслуживающий файл B во время записи файла A. Таким образом, вы избежите слишком длительной блокировки.

2. Спасибо за ваш ответ. К сожалению, у меня нет контроля над кодом клиентов Reader.