Usuário com melhor resposta
Alto consumo de memória

Pergunta
-
Bom dia,
Estou desenvolvendo uma aplicação em WPF que tem a funcionalidade de realizar backup/restore em bases Firebird. É uma aplicação relativamente pequena, com apenas 3 opções de execução, 1 de configuração e 1 de log. O aplicativo trabalha com ADO.NET, com provider para Firebird, utiliza recursos de FTP, compactação e descompactação e executa processos em background.
Na parte visual, estou utilizando um tema (ReuxablesLegacy.dll) e uma imagem de fundo em todas as pages (PNG de 60Kb), definida através de um style aplicado nestas pages. Em apenas 1 das páginas utilizo o recurso de opacidade, que já li que não é muito recomendado, justamente por conta de consumo de memória.
Rodando a aplicação em um Windows 7 (com todos os updates e services pack) a aplicação só no start chega a consumir 113Mb de memória. Conforme as opções vão sendo utilizadas, a memória vai acumulando, e chegou ao montante de 513Mb.
No geral a aplicação possui um bom desempenho, mas me preocupa a quantidade de memória sendo consumida e que pode dificultar a utilização em máquinas com menos recursos.
Alguém passou por mesma experiência ou possui dicas para auxiliar?
Grato,
Rodrigo
- Tipo Alterado AndreAlvesLimaModerator quinta-feira, 10 de março de 2011 16:28
Respostas
-
Pela descrição da aplicação não deveria consumir tanto, desenvolvo sistemas em WPF com charts de controle no Startup e consomem 30 mb no máximo, recomendo utilizar o Profiler do Visual Studio para identificar mais rapidamente seu problema (Visual Studio Ultimate/Visual Studio Premium). Caso não use essas versões me diga o que você faz ao carregar a aplicação?
Link que pode te auxiliar:
http://msdn.microsoft.com/en-us/library/ms182376.aspx
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Ola
Vc esta usando em algun item de sua aplicação o efeito DropShadowEffect?
Retire esse tratamento e não apresentara mais o problema de performance
- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Rodrigo,
Uma coisa que você poderia tentar pra ver se melhora é setar para 0 o UndoLimit do seu TextBox...
André Alves de Lima
Microsoft MVP - Client App Dev
Visite o meu site: http://www.andrealveslima.com.br
Me siga no Twitter: @andrealveslima- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Rodrigo,
Tente não trabalhar diretamente com o TextBox.AppendText, por que você não trabalha com vinculação de dados (DataBinding) verifique o pattern MVVM é ótimo para trabalhar com WPF, acredito que terá uma melhor performance, na vinculação de dados utilize um objeto StringBuilder para ir adicionado as mensagens de evento. Algo desse tipo:
private StringBuilder _log; public string Log { get { if (_log == null) _log = new StringBuilder(); return _log.ToString(); } set { if (_log == null) _log = new StringBuilder(); _log.Append(value); } }
Outra coisa, caso a notificação de eventos (Log) seja algo muito rapido, como varias vezes por segundo, você não irá querer executar o método ScrollToEnd nesta velocidade, o usuário não iria conseguir fazer a leitura :), então esse método pode ser executado a cada 3-5 segundos. Utilize um objeto System.Threading.Timer para o agendamento deste método.
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
De uma olhada no artigo que escrevi sobre a utilização do objeto BackgroundWorker, WPF, MVVM, no link: http://paulosouza.net/Article/Details/2 , pode te auxiliar.
Exemplo de aplicação:
<Window x:Class="WpfAsync.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <TextBox Name="textBoxLog" Text="{Binding Log}" Height="129" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="12,12,0,0" VerticalAlignment="Top" Width="479" /> <Button Name="button1" Command="{Binding ProcessUpdateCommand}" Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,147,0,0" VerticalAlignment="Top" Width="75" /> </Grid> </Window>
De uma olhanda no CodePlex pelo componente WPFToolkit.Extended tem alguns objetos legais.namespace WpfAsync { public class MainWindowViewModel : AsyncPresentationModel { public RelayCommand ProcessUpdateCommand { get { if (_processUpdateCommand == null) { _processUpdateCommand = new RelayCommand( param => { this.ProcessAsync((sender, e) => { var worker = sender as BackgroundWorker; worker.WorkerReportsProgress = true; worker.ProgressChanged += (sender1, e1) => { this.NotifyPropertyChanged<string>(() => this.Log); }; for (int i = 0; i < 100; i++) { Log = string.Format("Atualização {0}", i); Thread.Sleep(TimeSpan.FromSeconds(0.5)); worker.ReportProgress(i); } }, (sender, e) => { this.NotifyPropertyChanged<string>(() => this.Log); }, "Atualizando..."); }, param => true); } return _processUpdateCommand; } } private RelayCommand _processUpdateCommand; public string Log { get { if (_log == null) _log = new StringBuilder(); return _log.ToString(); } set { if (_log == null) _log = new StringBuilder(); _log.Append(value); } } private StringBuilder _log; } }
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Sugerido como Resposta Rafael Lauratto sexta-feira, 18 de março de 2011 16:58
- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
Todas as Respostas
-
Pela descrição da aplicação não deveria consumir tanto, desenvolvo sistemas em WPF com charts de controle no Startup e consomem 30 mb no máximo, recomendo utilizar o Profiler do Visual Studio para identificar mais rapidamente seu problema (Visual Studio Ultimate/Visual Studio Premium). Caso não use essas versões me diga o que você faz ao carregar a aplicação?
Link que pode te auxiliar:
http://msdn.microsoft.com/en-us/library/ms182376.aspx
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Obrigado pela resposta Paulo.
Um dos motivos de tanto consumo de memória consegui identificar. A minha aplicação estava sendo monitorada pelo Application Verifier. Após desabilitar o monitoramento um montante desta memória deixou de ser alocada.
Estava desconfiado dos recursos visuais da aplicação, mas removi tema, imagens e mesmo assim a memória continuou alta.
Iniciei a análise da aplicação através do Profiler como você recomendou. Vamos ver o que identificarei de problema.
Encontrando alguma coisa postarei aqui no forum.
Grato,
Rodrigo
-
Ola
Vc esta usando em algun item de sua aplicação o efeito DropShadowEffect?
Retire esse tratamento e não apresentara mais o problema de performance
- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Obrigado pela resposta Rafael.
Em minha aplicação não utilizo este recurso, e também não tenho nenhum controle com AllowsTransparency habilitado. O máximo que utilizo é modificar a propriedade Opacity de algumas imagens para dar um efeito de desabilitado (tenho botões com imagens dentro, quando executo enable=false no botão, a imagem continua da mesma forma, então modifico via código a propriedade opacity).
Grato,
Rodrigo
-
Bom dia Paulo,
Agradeço pela dica sobre o Profiler, ótima ferramenta do Visual Studio. Através dela consegui identificar dois grandes gargalos na aplicação, causadores de um alto consumo de memória, explico: como o objetivo da aplicação é realizar backup/restore de banco de dados, no caso do Firebird, executo nativamente o aplicativo de linha de comando através de CreateProcess. Durante a execução capturo a saída da linha de comando e exibo para o usuário dentro de um TextBox, acumulando cada linha exibida, o que no final me gera um log de como o processo foi executado. Para adicionar o texto ao TextBox executo o método AppendText (este é o primeiro gargalo), mas o texto adicionado não é exibido para o usuário, porque são muitas linhas, então logo em seguida executo o método ScrollToEnd (este é o segundo gargalo). Os dois métodos juntos, ao final do processo totalizam 78Mb.
Após esta longa descrição pergunto, existe um método menos custoso para informar o usuário do que está acontecendo?
Grato,
Rodrigo
-
Rodrigo,
Uma coisa que você poderia tentar pra ver se melhora é setar para 0 o UndoLimit do seu TextBox...
André Alves de Lima
Microsoft MVP - Client App Dev
Visite o meu site: http://www.andrealveslima.com.br
Me siga no Twitter: @andrealveslima- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Rodrigo,
Tente não trabalhar diretamente com o TextBox.AppendText, por que você não trabalha com vinculação de dados (DataBinding) verifique o pattern MVVM é ótimo para trabalhar com WPF, acredito que terá uma melhor performance, na vinculação de dados utilize um objeto StringBuilder para ir adicionado as mensagens de evento. Algo desse tipo:
private StringBuilder _log; public string Log { get { if (_log == null) _log = new StringBuilder(); return _log.ToString(); } set { if (_log == null) _log = new StringBuilder(); _log.Append(value); } }
Outra coisa, caso a notificação de eventos (Log) seja algo muito rapido, como varias vezes por segundo, você não irá querer executar o método ScrollToEnd nesta velocidade, o usuário não iria conseguir fazer a leitura :), então esse método pode ser executado a cada 3-5 segundos. Utilize um objeto System.Threading.Timer para o agendamento deste método.
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Boa tarde Paulo, e novamente obrigado pela resposta.
Conheço o pattern MVVM, mas tenho uma limitação neste processo. Ele está sendo executado através de um BackgroundWorker, para manter o status da aplicação como "Executando" e permitir ao usuário o cancelamento do processo. É através do evento DoWork que crio a instância de minha classe de Backup, e esta classe é quem possui a propriedade "Log", como sugerido.
Acontece que através da thread criada pelo BackgroundWorker não tenho como modificar nenhum componente/classe da thread principal. O único meio de realizar a comunicação é através do evento ProgressChanged, no qual envio a nova linha de log e utilizo o AppendText do TextBox.
Analisando o evento BackgroundWorker.ProgressChanged, é possível transferir um objeto de uma thread para outra. Não sei se por este canal conseguiria realizar DataBinding com o TextBox?!
Grato,
Rodrigo
-
Boa tarde André, obrigado pela resposta.
Realizei a alteração sugerida, mas não obtive resultado, continuando o mesmo consumo de memória. O componente TextBox estava configurado como ReadOnly, não sei se esta propriedade pode interferir na questão do Undo.
Grato,
Rodrigo.
-
De uma olhada no artigo que escrevi sobre a utilização do objeto BackgroundWorker, WPF, MVVM, no link: http://paulosouza.net/Article/Details/2 , pode te auxiliar.
Exemplo de aplicação:
<Window x:Class="WpfAsync.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525"> <Grid> <TextBox Name="textBoxLog" Text="{Binding Log}" Height="129" HorizontalAlignment="Left" TextWrapping="Wrap" Margin="12,12,0,0" VerticalAlignment="Top" Width="479" /> <Button Name="button1" Command="{Binding ProcessUpdateCommand}" Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,147,0,0" VerticalAlignment="Top" Width="75" /> </Grid> </Window>
De uma olhanda no CodePlex pelo componente WPFToolkit.Extended tem alguns objetos legais.namespace WpfAsync { public class MainWindowViewModel : AsyncPresentationModel { public RelayCommand ProcessUpdateCommand { get { if (_processUpdateCommand == null) { _processUpdateCommand = new RelayCommand( param => { this.ProcessAsync((sender, e) => { var worker = sender as BackgroundWorker; worker.WorkerReportsProgress = true; worker.ProgressChanged += (sender1, e1) => { this.NotifyPropertyChanged<string>(() => this.Log); }; for (int i = 0; i < 100; i++) { Log = string.Format("Atualização {0}", i); Thread.Sleep(TimeSpan.FromSeconds(0.5)); worker.ReportProgress(i); } }, (sender, e) => { this.NotifyPropertyChanged<string>(() => this.Log); }, "Atualizando..."); }, param => true); } return _processUpdateCommand; } } private RelayCommand _processUpdateCommand; public string Log { get { if (_log == null) _log = new StringBuilder(); return _log.ToString(); } set { if (_log == null) _log = new StringBuilder(); _log.Append(value); } } private StringBuilder _log; } }
Atenciosamente, Paulo R. Pereira de Souza
http://paulosouza.net
E-mail: paulorpereirasouza@hotmail.com.- Sugerido como Resposta Rafael Lauratto sexta-feira, 18 de março de 2011 16:58
- Marcado como Resposta AndreAlvesLimaModerator sexta-feira, 17 de junho de 2011 11:52
-
Rodrigo,
Conseguiu solucionar essa questão?
André Alves de Lima
Microsoft MVP - Client App Dev
Visite o meu site: http://www.andrealveslima.com.br
Me siga no Twitter: @andrealveslima