Стратегия доступа к файлам в многопоточной среде (веб-приложение)
-
09-06-2019 - |
Вопрос
У меня есть файл, представляющий собой XML-представление некоторых данных, которые берутся из веб-службы и кэшируются локально в веб-приложении.Идея заключается в том, что эти данные являются очень статичный, но просто мог бы измениться.Итак, я настроил его на кэширование в файл и подключил к нему монитор, чтобы проверить, был ли он удален.После удаления файл будет обновлен из исходного кода и перестроен заново.
Однако сейчас я сталкиваюсь с проблемами, потому что, очевидно, в многопоточной среде он падает, когда пытается получить доступ к данным, когда он все еще читает / записывает файл.
Это сбивает меня с толку, потому что я добавил объект для блокировки, и он всегда блокируется во время чтения / записи.Насколько я понимаю, при попытке доступа из других потоков будет указано "подождать", пока блокировка не будет снята?
Просто чтобы вы знали, я новичок в многопоточной разработке, поэтому я полностью готов признать, что это ошибка с моей стороны :)
- Я что-то упускаю?
- Какова наилучшая стратегия доступа к файлам в многопоточной среде?
Редактировать
Извините - я должен был сказать, что это использование ASP.NET 2.0 :)
Решение
Вот код, который я использую, чтобы убедиться, что файл не заблокирован другим процессом.Он не на 100% надежен, но большую часть времени он выполняет свою работу:
/// <summary>
/// Blocks until the file is not locked any more.
/// </summary>
/// <param name="fullPath"></param>
bool WaitForFile(string fullPath)
{
int numTries = 0;
while (true)
{
++numTries;
try
{
// Attempt to open the file exclusively.
using (FileStream fs = new FileStream(fullPath,
FileMode.Open, FileAccess.ReadWrite,
FileShare.None, 100))
{
fs.ReadByte();
// If we got this far the file is ready
break;
}
}
catch (Exception ex)
{
Log.LogWarning(
"WaitForFile {0} failed to get an exclusive lock: {1}",
fullPath, ex.ToString());
if (numTries > 10)
{
Log.LogWarning(
"WaitForFile {0} giving up after 10 tries",
fullPath);
return false;
}
// Wait for the lock to be released
System.Threading.Thread.Sleep(500);
}
}
Log.LogTrace("WaitForFile {0} returning true after {1} tries",
fullPath, numTries);
return true;
}
Очевидно, вы можете настроить таймауты и повторные попытки в соответствии с вашим приложением.Я использую это для обработки огромных FTP-файлов, запись которых занимает некоторое время.
Другие советы
Если вы фиксируете объект, хранящийся как статический тогда блокировка должна работать для всех потоков в одном Домене приложения, но, возможно, вам нужно загрузить пример кода, чтобы мы могли взглянуть на строки-нарушители.
Тем не менее, одной из идей было бы проверить, настроен ли IIS для запуска в Веб-сад режим (т. е.более 1 процесса, выполняющего ваше приложение), что нарушило бы вашу логику блокировки.Хотя вы могли бы исправить такую ситуацию с помощью мьютекса, было бы проще перенастроить ваше приложение для выполнения в рамках одного процесса, хотя было бы разумно проверять производительность до и после изменения настроек web garden, поскольку это потенциально может повлиять на производительность.
Возможно, вы могли бы создать файл с временным именем («data.xml_TMP»), а когда он будет готов, изменить имя на то, которое оно должно быть.Таким образом, ни один другой процесс не сможет получить к нему доступ, пока он не будет готов.
Хорошо, я работал над этим и в итоге создал модуль стресс-тестирования, который, по сути, выбивает всякую ерунду из моего кода из нескольких потоков (См. связанный вопрос).
С этого момента стало намного проще находить дыры в моем коде.Оказывается, мой код на самом деле был не так уж и далёк, но существовал определённый логический путь, по которому он мог войти, что в основном приводило к накоплению операций чтения/записи, то есть, если бы они не были очищены вовремя, это бы иди бум!
Как только я его вынул, снова провел стресс-тест, все заработало нормально!
Так что я особо ничего не делал особенный в моем коде доступа к файлу, просто убедился, что я использовал lock
заявления, где это уместно (т.е.при чтении или письме).
Как насчет использования AutoResetEvent
для связи между потоками?Я создал консольное приложение, которое создает файл размером примерно 8 ГБ в createfile
метод, а затем скопируйте этот файл в main
метод
static AutoResetEvent waitHandle = new AutoResetEvent(false);
static string filePath=@"C:\Temp\test.txt";
static string fileCopyPath=@"C:\Temp\test-copy.txt";
static void Main(string[] args)
{
Console.WriteLine("in main method");
Console.WriteLine();
Thread thread = new Thread(createFile);
thread.Start();
Console.WriteLine("waiting for file to be processed ");
Console.WriteLine();
waitHandle.WaitOne();
Console.WriteLine();
File.Copy(filePath, fileCopyPath);
Console.WriteLine("file copied ");
}
static void createFile()
{
FileStream fs= File.Create(filePath);
Console.WriteLine("start processing a file "+DateTime.Now);
Console.WriteLine();
using (StreamWriter sw = new StreamWriter(fs))
{
for (long i = 0; i < 300000000; i++)
{
sw.WriteLine("The value of i is " + i);
}
}
Console.WriteLine("file processed " + DateTime.Now);
Console.WriteLine();
waitHandle.Set();
}