Pergunta

Eu tenho um arquivo que é uma representação XML de alguns dados obtidos de um serviço da Web e armazenados em cache localmente em um aplicativo da Web.A ideia é que esses dados sejam muito estático, mas apenas poder mudar.Então, configurei-o para armazenar em cache um arquivo e coloquei um monitor nele para verificar se ele foi excluído.Depois de excluído, o arquivo será atualizado de sua origem e reconstruído.

Agora estou tendo problemas, porque obviamente em um ambiente multithread ele falha ao tentar acessar os dados quando ainda está lendo/gravando o arquivo.

Isso está me confundindo, porque adicionei um objeto para bloquear, e ele é sempre bloqueado durante a leitura/gravação.Entendo que a tentativa de acesso de outros threads seria instruída a "esperar" até que o bloqueio fosse liberado.

Só para você saber, sou realmente novo no desenvolvimento multithread, então estou totalmente disposto a aceitar que isso é uma bagunça da minha parte :)

  • Estou esquecendo de algo?
  • Qual é a melhor estratégia de acesso a arquivos em um ambiente multithread?

Editar

Desculpe - eu deveria ter dito que isso está usando ASP.NET 2.0 :)

Foi útil?

Solução

Aqui está o código que uso para garantir que um arquivo não seja bloqueado por outro processo.Não é 100% infalível, mas dá conta do recado na maioria das vezes:

    /// <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;
    }

Obviamente, você pode ajustar os tempos limite e as novas tentativas para se adequar à sua aplicação.Eu uso isso para processar arquivos FTP enormes que demoram um pouco para serem gravados.

Outras dicas

Se você estiver bloqueando um objeto armazenado como um estático então o bloqueio deve funcionar para todos os threads no mesmo domínio de aplicativo, mas talvez você precise fazer upload de um exemplo de código para que possamos dar uma olhada nas linhas incorretas.

Dito isto, uma ideia seria verificar se o IIS está configurado para rodar em Jardim da Web modo (ou seja,mais de um processo executando seu aplicativo) o que quebraria sua lógica de bloqueio.Embora você possa corrigir tal situação com um mutex, seria mais fácil reconfigurar seu aplicativo para ser executado em um único processo, embora seja aconselhável verificar o desempenho antes e depois de mexer nas configurações do web garden, pois isso pode afetar potencialmente desempenho.

Você poderia talvez criar o arquivo com um nome temporário ("data.xml_TMP") e, quando estiver pronto, alterar o nome para o que deveria ser.Dessa forma, nenhum outro processo o acessará antes que esteja pronto.

OK, estou trabalhando nisso e acabei criando um módulo de teste de estresse para basicamente eliminar meu código de vários threads (Veja a pergunta relacionada).

A partir de então foi muito mais fácil encontrar falhas no meu código.Acontece que meu código não estava muito longe, mas havia um certo caminho lógico no qual ele poderia entrar, o que basicamente fazia com que as operações de leitura/gravação se acumulassem, o que significa que se elas não fossem apagadas a tempo, seria explodir!

Depois que tirei isso, fiz meu teste de estresse novamente, tudo funcionou bem!

Então, eu realmente não fiz nada especial no meu código de acesso ao arquivo, apenas certifiquei-me de usar lock declarações quando apropriado (ou seja,ao ler ou escrever).

Que tal usar AutoResetEvent comunicar entre threads?Eu criei um aplicativo de console que cria arquivos de aproximadamente 8 GB em createfile método e, em seguida, copie esse arquivo em main método

 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();
    }
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top