Сохранение блокировки файлов в среде без сохранения состояния по нескольким запросам (Asp.Net Core) - PullRequest
0 голосов
/ 10 октября 2018

Я пишу приложение Asp.Net Core (с RazerPages), которое загружает / скачивает файлы.У меня есть элемент управления, который использует AJAX для загрузки файлов кусками.Файлы загружаются в подкаталог на сервере.Имя подкаталога - это guid, который генерируется при загрузке страницы.

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

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

Как создать мьютекс для подкаталога, с которым я сейчас работаю, и распознать его по нескольким запросам?

public class FileIOService : IFileService
{
    public string RootDiectory { get; set; }

    public void CreateDirectory(Guid id)
    {
        // Create the working directory if it doesn't exist
        string path = Path.Combine(RootDiectory, id.ToString());
        lock (id.ToString())
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
        }
    }

    public void AppendToFile(Guid id, string fileName, Stream content)
    {
        try
        {
            CreateDirectory(id);
            string fullPath = Path.Combine(RootDiectory, id.ToString(), fileName);
            lock (id.ToString())
            {
                bool newFile = !File.Exists(fullPath);
                using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                {
                    using (content)
                    {
                        content.CopyTo(stream);
                    }
                }
            }
        }
        catch (IOException ex)
        {
            throw;
        }
    }


    public void DeleteFile(Guid id, string fileName)
    {
        string path = Path.Combine(RootDiectory, id.ToString(), fileName);
        lock (id.ToString())
        {
            if (File.Exists(path))
            {
                File.Delete(path);
            }

            string dirPath = Path.Combine(RootDiectory, id.ToString());
            DirectoryInfo dir = new DirectoryInfo(dirPath);
            if (dir.Exists && !dir.GetFiles().Any())
            {
                Directory.Delete(dirPath, false);
            }
        }
    }

    public void DeleteDirectory(Guid id)
    {
        string path = Path.Combine(RootDiectory, id.ToString());
        lock (id.ToString())
        {
            if (Directory.Exists(path))
            {
                Directory.Delete(path, true);
            }
        }
    }
}

1 Ответ

0 голосов
/ 11 октября 2018

В итоге я не использовал lock и вместо этого использовал явно созданные глобальные мьютексы.Я создал приватный метод ProtectWithMutex, который выполнял бы действие в защищенном блоке кода:

    /// <summary>
    /// Performs the specified action. Only one action with the same Guid may be executing
    /// at a time to prevent race-conditions.
    /// </summary>
    private void ProtectWithMutex(Guid id, Action action)
    {
        // unique id for global mutex - Global prefix means it is global to the machine
        string mutexId = string.Format("Global\\{{{0}}}" ,id);
        using (var mutex = new Mutex(false, mutexId, out bool isNew))
        {
            var hasHandle = false;
            try
            {
                try
                {
                    //change the timeout here if desired.
                    int timeout = Timeout.Infinite;
                    hasHandle = mutex.WaitOne(timeout, false);
                    if (!hasHandle)
                    {
                        throw new TimeoutException("A timeout occured waiting for file to become available");
                    }
                }
                catch (AbandonedMutexException)
                {
                    hasHandle = true;
                }
                TryAndRetry(action);
            }
            finally
            {
                if (hasHandle)
                    mutex.ReleaseMutex();
            }
        }
    }

Это предотвратило попытки нескольких запросов одновременно манипулировать одним и тем же каталогом, но у меня все еще были проблемыгде другие процессы (Windows Explorer, Antivirus, я не совсем уверен) захватывали файл между запросами.Чтобы обойти это, я создал TryAndRetry метод, который будет пытаться выполнять одно и то же действие снова и снова до тех пор, пока оно не будет успешным (или до тех пор, пока оно не завершится неудачей слишком много раз):

    /// <summary>
    /// Trys to perform the specified action a number of times before giving up.
    /// </summary>
    private void TryAndRetry(Action action)
    {
        int failedAttempts = 0;
        while (true)
        {
            try
            {
                action();
                break;
            }
            catch (IOException ex)
            {
                if (++failedAttempts > RetryCount)
                {
                    throw;
                }
                Thread.Sleep(RetryInterval);
            }
        }
    }

Все, что мне нужно было сделать при этомсмысл был заменить все мои lock блоки на вызовы моего Protected метода:

public void AppendToFile(Guid id, string fileName, Stream content)
{
    CreateDirectory(id);
    string dirPath = Path.Combine(RootDiectory, id.ToString());
    string fullPath = Path.Combine(dirPath, fileName);
    ProtectWithMutex(id, () =>
    {
        using (FileStream stream = new FileStream(fullPath, FileMode.Append, FileAccess.Write, FileShare.None))
        {
            using (content)
            {
                content.CopyTo(stream);
            }
        }
    });
}

public void DeleteFile(Guid id, string fileName)
{
    string path = Path.Combine(RootDiectory, id.ToString(), fileName);
    ProtectWithMutex(id, () =>
    {
        if (File.Exists(path))
        {
            File.Delete(path);
        }

        string dirPath = Path.Combine(RootDiectory, id.ToString());
        DirectoryInfo dir = new DirectoryInfo(dirPath);
        if (dir.Exists && !dir.GetFiles().Any())
        {
            Directory.Delete(dirPath, false);
        }
    });
}
...