Olá pessoal, tudo certo?
Recentemente, tive um problema ao tentar processar um arquivo em uma thread separada da main thread da aplicação.
Para vocês entenderem o que eu queria fazer, essa era a situação: O usuário faz o upload de um arquivo (.xlsx) e a aplicação faz diversos processamentos com as informações desse arquivo.
Bom, a manipulação desse arquivo não é nada rápida e eu não queria travar a thread principal da aplicação, deixando o usuário “preso” com um load de vários minutos na tela. Tive duas ideias para resolver isso, são elas:
Implementar um serviço fora da aplicação, que fosse chamado quando tivesse um novo arquivo para processar.
A segunda ideia foi, na mesma aplicação, “startar” uma nova thread para fazer o trabalho. Escolhi a segunda alernativa, vejam o problema que tive e como resolvi.
O código inicial
//Controller
[HttpPost] public ActionResult Processo(HttpPostedFileBase arquivo) { try { var stream = arquivo.InputStream; new Thread(() => ProcessarArquivo(stream)).Start(); TempData["MensagemSucesso"] = "O arquivo está sendo processado."; } catch (Exception ex) { TempData["MensagemErro"] = ex.Message; } return RedirectToAction("MinhaView"); }
//Metodo que processa o arquivo
private void ProcessarArquivo(Stream file) { var arquivo = ExcelReaderFactory.CreateOpenXmlReader(file); //... //... }
Ao executar o código acima, uma NullReferenceException estourava no método ProcessarArquivo. O interessante, é que a Exception hora acontencia em uma linha, hora em outra.
Pesquisando o que poderia estar acontecendo, acabei encontrando um problema parecido com o meu. Aparentemente, a classe HttpPostedFileBase dá um Dispose() quando a action onde ela foi criada é finalizada. Como um outro processo começava a ser executado em parelelo, ao terminar de executar a action Processo que recebe o HttpPostedFileBase, o .net dava o Dispose e a aplicação perdia a referência do Stream que estava sendo manipulado. Por esse motivo, a exception não acontecia sempre no mesmo local, pois, o valor só era perdido quando a action Processo era finalizada.
A resolução
Ao descobrir o que estava acontecendo, ficou fácil pensar em soluções. A mais rápida e simples que veio em mente foi criar uma nova instância de Stream e copiar o valor que estava recebendo por parâmetro e passar essa cópia como argumento para o método ProcessarArquivo. E foi assim que fiz, veja:
//Controller
[HttpPost] public ActionResult Processo(HttpPostedFileBase arquivo) { try { var stream = arquivo.InputStream; var memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); new Thread(() => ProcessarArquivo(memoryStream)).Start(); TempData["MensagemSucesso"] = "O arquivo está sendo processado."; } catch (Exception ex) { TempData["MensagemErro"] = ex.Message; } return RedirectToAction("MinhaView"); }
O método ProcessarArquivo não teve alteração. Com essa implementação, tudo funcionou lindo.
Alguém precisou implementar algo parecido? Tem uma solução diferente? Compartilhe conosco nos comentários 🙂
Até o próximo post!
Link’s úteis
Excel Data Reader – Read Excel files in .NET
Wennder Santos