C# - Gerenciando Processos e Threads em formulários Windows Forms - I

 Hoje vamos tratar do gerenciamento de processos e threads em aplicações Windows Forms

Você pode usar o componente Process para obter uma lista de processos em execução, iniciar e parar processos e retornar informação sobre os processos em execução em um computador.  

A classe Process esta no namespace using System.Diagnostics;

Recursos usados

Objetivos

Aprendizado

Conceitos Básicos

Um processo é um aplicativo em execução. Uma Thread ou segmento é a unidade básica para a qual o sistema operacional aloca tempo do processador. Uma thread pode executar qualquer parte do código do processo, incluindo partes atualmente sendo executadas por outra thread.

Assim podemos usar uma instância do componente Process para determinar se um processo parou de responder. Se o valor retornado for igual a true você pode forçar o componente a parar o processo ou avisar o usuário e oferecer a ele opções de escolha sobre o que fazer.

Nota: Processos de 32 bits não podem acessar os módulos de um processo de 64 bits. Se você tentar obter informações sobre um processo de 64 bits a partir de um processo de 32 bits, você receberá uma exceção Win32Exception.

O componente Process pode ser usado para iniciar processo no seu sistema pela chamada do método Start.

O método Start é usado em conjunto com a propriedade StartInfo.

A propriedade StartInfo define propriedades que são passadas para o método Start.

Antes de chamar o método Start você precisa especificar o nome do arquivo do processo a iniciar definindo a propriedade FileName ou informando o caminho completo do processo e, no caso de aplicações Windows, informar apenas o nome do processo.

Podemos usar essa sobrecarga do método Start para iniciar um recurso do processo e associá-lo com o componente do processo atual. O valor de retorno true indica que um novo recurso do processo foi iniciado. Se o recurso de processo especificado pelo membro FileName da propriedade StartInfo já está em execução no computador, nenhum recurso adicional do processo é iniciado. Em vez disso, o recurso de processo em execução é reutilizado e false é retornado.

Vejamos um exemplo bem básico de uma aplicação Windows Forms que contém um formulário com um controle Button que no seu evento Click chama o processo para executar o NotePad (Bloco de Notas):

Abra o Visual Studio 2012 Express for Windows Desktop e clique em New Project;

Selecione o template Visual C# -> Windows -> Windows Forms Application e informe o nome Processo_Threads e clique em OK;

Para cada exemplo iremos incluir um novo projeto na solução usando o menu File -> Add -> New Project;

A seguir o código e o resultado da execução do exemplo:

using System.Diagnostics;
using System.Windows.Forms;

namespace Processo_Threads
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnExecutarProcesso_Click(object sender, EventArgs e)
        {
            Process macProcesso = new Process();
            macProcesso.StartInfo.FileName = "Notepad";
            macProcesso.StartInfo.WindowStyle = ProcessWindowStyle.Maximized;
            macProcesso.Start();

        }
    }
}

Obtendo informação  do processo atual

Quais informações podemos obter da classe Process ? A seguir temos uma tabela com uma relação dos principais membros da classe:

Propriedades públicas:

BasePriority

Obtêm a base prioritária do processo associado.

Container

Obtêm a IContainer que contém o componente

EnableRaisingEvents

Obtêm ou define se o evento Exited será disparado quando o processo terminar.

ExitCode

Obtêm um valor para o processo associado quando o mesmo é encerrado.

ExitTime

Obtêm a hora que o processo associado encerrou.

Handle

Retorna o handle do processo associado nativo.

HandleCount

Obtêm o número de handles abertos pelo  processo.

HasExited

Obtêm um valor indicando se o processo associado terminou.

Id

Obtêm o identificador único para o processo associado.

MachineName

Obtêm o nome do computador no qual o processo associado esta rodando.

MainModule

Obtêm o módulo principal do  processo associado.

MainWindowHandle

Obtêm o handle da janela para a janela principal do processo associado.

MainWindowTitle

Obtêm o titulo da janela principal do processo.

ProcessName

Obtêm o nome do processo.

Responding

Obtêm um valor indicando se a interface do usuário do processo esta respondendo

Site

Obtêm ou define o Site do componente.

StartTime

Obtêm a hora que o processo associado foi iniciado.

Threads

Obtêm o conjunto de threads que estão rodando no processo associado.

TotalProcessorTime

Obtêm a tempo total de processamento para este processo.

UserProcessorTime

Obtêm o tempo de processamento este processo.

VirtualMemrySize

Obtêm o tamanho da memória virtual do processo.

WorkingSet

Obtêm a memória física associada ao processo atual.

No exemplo a seguir temos a exibição de algumas informações do processo em execução em minha máquina local.

O código esta associado ao evento Click de um botão de comando - btnInfo -  em um formulário Windows Forms e o resultado esta sendo exibido em um listbox - lbInfo :

using System;
using System.Diagnostics;
using System.Windows.Forms;

namespace Processos_Informacoes
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void btnInfo_Click(object sender, EventArgs e)
        {
          
 Process proc = Process.GetCurrentProcess();
           lbInfo.Items.Add("Identificador do processo : " + proc.Id.ToString());
           lbInfo.Items.Add("Nome do Processo: " + proc.ProcessName);
           lbInfo.Items.Add("Handle do processo: " + proc.Handle.ToString());
           lbInfo.Items.Add("Titulo da janela principal do processo : " + proc.MainWindowTitle);
           lbInfo.Items.Add("Informação da base prioritária : " + proc.BasePriority);
           lbInfo.Items.Add("Número de handles abertos pelo processo : " + proc.HandleCount.ToString());
           lbInfo.Items.Add("A interface do usuário esta respondendo ? " + proc.Responding.ToString());

        }
    }
}

O método GetCurrentProcess() obtém um novo componente Process e o associa ao processo ativo.

Parando um Processo

Você pode usar a propriedade Responding para determinar se a interface do usuário de um processo está respondendo. Quando você tenta ler a propriedade Respondendo , um pedido é enviado para a interface de usuário do processo de destino . Se houver uma resposta imediata, o valor da propriedade de retorno é true; se não houver resposta da interface o valor retornado será false. Esta propriedade é útil se você precisar forçar um aplicativo que esta 'congelado' a ser fechado.

Há dois métodos que você pode usar para parar um processo com um componente Process. O método utilizado depende do tipo de processo a ser parado:

O exemplo a seguir mostra como determinar se o bloco de notas está respondendo. Se a propriedade Response for igual a true, chamamos o método CloseMainWindow para fechar o aplicativo . Se a propriedade Response for igual a false, chamamos o método Kill para forçar o fechamento do processo.

using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace Processo_Parar
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void btnPararProcesso_Click(object sender, EventArgs e)
        {
            // Define um array 
            Process[] macProcessos;
            Process macProcess = null;

            macProcess = Process.Start("NotePad.exe");

            if (macProcess.Responding)
            {
                MessageBox.Show("Status Processo do NotePad = Em execução");
                // Retorna um array contendo todas as instãncias do NotePad
                macProcessos = Process.GetProcessesByName("Notepad");
                // Percorre todos os processos e usando o método 
                // CloseMainWindow() para encerrar o Processo
                foreach (Process processo in macProcessos)
                {
                    processo.CloseMainWindow();
                }
            }
            else
            {
                MessageBox.Show("Status Processo do NotePad = Não Respondendo");
            }
        }
    }
}

Esperando um processo terminar

Um processo é dito ser ocioso quando sua janela principal está aguardando uma entrada do sistema. A fim de testar o processo para o seu estado de repouso, você deve primeiro ligar um componente de processo a ele. Você pode chamar o método WaitForInputIdle antes do processo de destino executar uma ação.

O método WaitForInputIdle instrui um componente do processo para aguardar o processo associado inserir um estado ocioso. O método é útil, por exemplo, quando o aplicativo espera por um processo para terminar de criar a sua janela principal antes de se comunicar com essa janela. O método WaitForInputIdle só funciona com processos que têm uma interface de usuário.

O método WaitForExit(int32) instrui o componente Process a esperar um número especificado de milisegundos para que o processo associado encerre. O método é usado para fazer a thread atual esperar até que o processo associado termine. Se um valor não for fornecido o processo aguarda indefinidamente.

Quando um processo associado termina (quer por shutdown do sistema operacional ou através de uma encerramento anormal) o sistema armazena informações sobre o processo e retorna o componente que chamou o método WaitForExit(int32).

Vejamos um exemplo de utilização de cada um destes métodos:

1- WaitForExit(int32)

using System;
using System.Diagnostics;
using System.Windows.Forms;
namespace Processo_Esperando_Terminar
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void btnEsperandoTerminar_Click(object sender, EventArgs e)
        {
            Process macProcesso = null;
            try
            {
                // inicia o processo
                macProcesso = Process.Start("NotePad.exe");
                // exibe estatisticas do processo ate o usuário fechar o programa
                do
                {
                    if (!macProcesso.HasExited)
                    {
                        // Atualiza os valores das propriedades do processo atual
                        macProcesso.Refresh();
                       lbInfo.Items.Add("");
                        // Exibe estatisticas 
                       lbInfo.Items.Add(macProcesso.ToString());
                       lbInfo.Items.Add("---------------------------------------------------------------");
                       lbInfo.Items.Add("  Uso da memória física : " + macProcesso.WorkingSet64.ToString());
                       lbInfo.Items.Add("  Tempo processador usuário : " + macProcesso.UserProcessorTime.ToString());
                       lbInfo.Items.Add("  tempo processador total : " + macProcesso.TotalProcessorTime.ToString());

                       if (macProcesso.Responding)
                        {
                           lbInfo.Items.Add("Status = Rodando");
                        }
                        else
                        {
                           lbInfo.Items.Add("Status = Não Respondendo");
                        }
                    }
                }
                while (!macProcesso.WaitForExit(1000));
               lbInfo.Items.Add("Código de saida do Processo : " +  macProcesso.ExitCode.ToString());
            }
            finally
            {
                if (macProcesso != null)
                {
                    macProcesso.Close();
                }
            }
        }
    }
}

2- WaitForInputIdle 

Faz com que o componente Process aguarde indefinidamente o processo associado entrar um estado ocioso.

Essa sobrecarga só se aplica a processos com uma interface de usuário e, portanto, um loop de mensagem.

using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
namespace Processo_Esperando_Terminar
{
    public partial class Form2 : Form
    {
        public Form2()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            //cria e inicia um processo para o NotePad
            Process process = new Process();
            process.StartInfo.FileName = "Notepad";
            process.Start();
            process.WaitForInputIdle();
            //espera 5 segundos
            Thread.Sleep(5000);
            if (!process.CloseMainWindow())
            {
                //mata o processo
                process.Kill();
            }
        }
    }
}

Atualizar um controle de formulário do Windows a partir de uma thread em segundo plano

Os controles Windows Forms só podem ser executados no segmento em que eles foram criados, ou seja, eles não são thread-safe . Se você quer obter ou definir propriedades ou chamar métodos em um controle a partir de uma thread em segundo plano a chamada deve ser empacotada para o segmento que criou o controle.

Assim se você tentar chamar um método de um formulário windows em uma thread distinta, uma exceção será disparada, e, dependendo de como você implementou o tratamento de exceção no seu código a aplicação pode encerrar. Se você não estiver efetuando um tratamento de exceção será exibida a seguinte mensagem:

  1. An unhandled exception of type 'System.ArgumentException' occurred in system.windows.forms.dll

Então para seu próprio bem tenha em mente sempre o seguinte:  "Quando você estiver trabalhando com controles em formulários windows tenha sempre certeza de somente acessar os controles a partir da thread na qual eles foram criados. senão..."

Criar e iniciar a thread

Quando uma thread é criada , uma nova instância da classe Thread é criada usando um construtor que leva o delegado ThreadStart como seu único parâmetro. No entanto, a thread não inicia a execução até que o método Start seja chamado. Quando Start é chamado, a execução começa na primeira linha do método referenciado pelo delegado ThreadStart .

private Thread timerThread;

timerThread = new Thread(new ThreadStart(ThreadProc));
timerThread.IsBackground = true;
timerThread.Start();

Chamar uma função a partir da thread

O delegado MethodInvoker fornece um delegado simples que é usado para chamar um método com uma lista de parâmetros vazia. Esse delegado pode ser usado para fazer chamadas para o método de invocação de um controle, ou quando você precisa de um simples delegado , mas não quer definir um você mesmo. Quando você cria um delegado MethodInvoker , você identifica o método que manipulará o evento. Para associar o evento com o manipulador de eventos , adicione uma instância do delegado para o evento. O manipulador de eventos é chamado sempre que o evento ocorre, a menos que você remova o delegado.

BeginInvoke executa o delegado dado no segmento que possui o identificador da janela subjacente do controle. O delegado é chamado de forma assíncrona e este método retorna imediatamente.

Você pode chamar isso de qualquer thread, mesmo a thread que possui o identificador do controle. Se o identificador do controle ainda não existe, isso fará o acompanhamento da cadeia o pai do controle até encontrar um controle ou formulário que tem um identificador de janela.

Se nenhum identificador apropriado puder ser encontrado, BeginInvoke irá lançar uma exceção. Exceções dentro do método de delegado são considerados não tratadas e serão enviadas ao manipulador de exceção não tratadas da aplicação.

Exemplo:

// Esta thread em segundo plano é chamada a partir do delegado ThreadStart
public void ThreadProc()
{
    try
    {
        MethodInvoker mi = new MethodInvoker(this.UpdateProgress);
        while (true)
        {
            this.BeginInvoke(mi);
            Thread.Sleep(500) ;
        }
    }
}

// Esta função é chamada a partir da thread em segundo plano
private void UpdateProgress()
{
    if (progressBar1.Value == progressBar1.Maximum)
    {
        progressBar1.Value = progressBar1.Minimum ;
    }
    progressBar1.PerformStep() ;
}
           

Esquematizando o processo temos :

- Chamadas assíncronas são feitas usando delegados. Um delegado é um objeto que envolve uma função. Delegados fornecem uma função síncrona e também fornece métodos para chamar a função envolveu de forma assíncrona.

- Esses métodos são BeginInvoke() e EndInvoke(). As listas de parâmetros destes métodos dependem da assinatura da função de delegado.

- BeginInvoke() é usado para iniciar a chamada assíncrona do método . Ele tem os mesmos parâmetros que o nome da função , e dois parâmetros adicionais. BeginInvoke () retorna imediatamente e não esperar que a chamada assíncrona para ser concluído. BeginInvoke( ) retorna um objeto IAsyncResult .

- A função EndInvoke() é utilizado para recuperar os resultados do método de chamada assíncrona . Ele pode ser chamado a qualquer momento após o método BeginInvoke() . Se a chamada assíncrona ainda não concluída, EndInvoke() bloqueia até que ela seja concluída. Os parâmetros da função EndInvoke() incluem os parâmetros out e ref que a função embrulhado tem , mais o objeto IAsyncResult que é retornado pelo método BeginInvoke ().

Como usar multithreading em aplicações com formulários windows ?

Veja bem, quando você for chamar um método de um controle que esta em um formulário windows fora da thread de origem do controle você tem que executar a chamada na thread de origem. (a chamada tem que ser 'marshalled').

Existem duas formas de você fazer isto:

  1. De forma assíncrona - Usar o método BeginInvoke que força o método a ser executado na thread que criou o formulário ou controle. Se você usar BeginInvoke a chamada irá retornar imediatamente. Se você precisar obter o valor de retorno de um delegate invocado de forma assíncrona você pode usar o EndInvoke com o IAsyncResult retornado pelo BeginInvoke para esperar até que o delegate invocado assincronamente tenha sido completado.

  2. De forma síncrona - Usar o método Invoke para obter o mesmo efeito. Quando você usar Invoke a thread atual será bloqueada até que o delegate tenha sido executado.

Todos os controles de formulários windows possuem o método Invoke e a propriedade InvokeRequired.

Você usa o método Invoke para fazer com que o controle transfira a chamada para a thread na qual ele foi criado e então executar a tarefa proposta.

A propriedade InvokeRequired retorna um valor true/false (false indica que a thread atual é a thread da fila de mensagem) indicando se quem chamou o método precisa usar o método Invoke quando a chamada é feita a um método do controle. Exemplo de uso:

Para encerrar utilize sempre estas duas regras de ouro (golden rules)

1- Nunca invoque qualquer método ou propriedade em um controle criado em outra thread sem usar os métodos/propriedades: Invoke, BeginInvoke, EndInvoke or CreateGraphics, e  InvokeRequired.

Cada controle esta vinculado a uma thread que executa a sua fila de mensagens. Se você tentar acessar ou alterar qualquer coisa na interface,(por exemplo alterar a propriedade Text) a partir de uma thread diferente você corre o risco de travar o seu programa.

2- Nunca execute uma tarefa que vai consumir muito tempo em uma thread de um controle/formulário windows forms.

Se o seu código esta sendo executado em uma thread de controle/formulário nenhum outro código está rodando nesta thread. O que significa que você não irá receber notificações de eventos, seus controles não serão redesenhados , etc. E você terá que esperar que o código termine de ser executado para realizar outras tarefas. Geralmente os programadores VB poderiam ficar tentados a usar uma chamada a Application.DoEvents. Mas isto não é aconselhável.

Depois de toda essa teoria na segunda parte do artigo veremos a sua aplicação em uma aplicação Windows Forms usando threads. 

João 1:14 E o Verbo se fez carne, e habitou entre nós, cheio de graça e de verdade; e vimos a sua glória, como a glória do unigênito do Pai.

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

 

             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter
 

Referências:


José Carlos Macoratti