Compartilhar via


Instruções passo a passo: criando um componente multithreaded simples com o Visual C#

O componente de BackgroundWorker substitui e adiciona funcionalidade ao namespace de System.Threading ; no entanto, o namespace de System.Threading é mantida para compatibilidade com versões anteriores e o uso futuro, se você escolher. Para mais informações, consulte Visão geral do componente BackgroundWorker.

Você pode escrever aplicativos que podem executar várias tarefas simultaneamente. Essa capacidade, multithreadingchamado, ou segmentação livre, são uma maneira poderosa para criar componentes que são de processamento intensivo e de exigir a entrada do usuário. Um exemplo de um componente que pode utilizar multithreading seria um componente que calcula informações de folha de pagamento. O componente pode processar os dados inseridos em um banco de dados por um usuário de um segmento quando os cálculos de processamento intensivo de folha de pagamento foram executados em outro. Executando esses processos em segmentos separados, os usuários não precise esperar o computador para concluir cálculos antes de inserir dados adicionais. Em essa explicação passo a passo, você criará um componente simples com vários que executa cálculos complexos simultaneamente.

Criando o projeto

Seu aplicativo consistirá em um único formulário e um componente. O usuário entrará com valores e sinal para o componente iniciar cálculos. O formulário em receberá valores de seu componente e exibir-os-&z em controles de rótulo. O componente irá executar cálculos de processamento intensivo e sinalizará o formulário quando completo. Você criará variáveis públicas no seu componente para manter valores recebidos da interface do usuário. Você também irá implementar métodos em seu componente para executar cálculos com base nos valores de essas variáveis.

Dica

Embora uma função geralmente é preferível para um método que calcula um valor, os argumentos não podem ser passados entre segmentos, nem podem os valores retornados.Há várias maneiras simples de fornecer valores para segmentos e de receber valores de eles.Em a demonstração, você valores de retorno para sua interface do usuário atualizando variáveis públicas, e eventos serão usados para notificar o programa principal quando um segmento concluiu a execução.

As caixas de diálogo e comandos de menu você vê podem diferir daquelas descritas na ajuda dependendo de suas configurações ativas ou versão.Para alterar suas configurações, escolha Import and Export Settings no menu Tools.Para mais informações, consulte Personalizando configurações de desenvolvimento no Visual Studio.

Para criar o formulário

  1. Criar um novo projeto Windows Application.

  2. Nomeie o aplicativo Calculations e renomear Form1.cs como frmCalculations.cs. Quando Visual Studio o solicita a renomear o elemento de código de Form1 , clique Sim.

    Este formulário servirá como a interface primária para seu aplicativo.

  3. Adicione cinco controles de Label , quatro controles de Button , e um controle de TextBox ao seu formulário.

  4. Definir propriedades para esses controles como segue:

    Controle

    Nome

    Texto

    label1

    lblFactorial1

    (em branco)

    label2

    lblFactorial2

    (em branco)

    label3

    lblAddTwo

    (em branco)

    label4

    lblRunLoops

    (em branco)

    label5

    lblTotalCalculations

    (em branco)

    button1

    btnFactorial1

    Factorial

    button2

    btnFactorial2

    Factorial - 1

    button3

    btnAddTwo

    Adicione dois

    button4

    btnRunLoops

    Executar um loop

    textBox1

    txtValue

    (em branco)

Para criar o componente da calculadora

  1. Em o menu de Projeto , selecione Adicionar Componente.

  2. Nomeie Calculatorcomponente.

Para adicionar variáveis públicas para o componente da calculadora

  1. Abra editor de códigos para Calculator.

  2. Adicione instruções para criar variáveis públicas que você usará para passar valores de frmCalculations a cada segmento.

    A variável manterá um total varTotalCalculations executando o número total de cálculos executados pelo componente, e os outros valores de variáveis receberá formulário.

    public int varAddTwo; 
    public int varFact1;
    public int varFact2;
    public int varLoopValue;
    public double varTotalCalculations = 0;
    

Para adicionar métodos e eventos para o componente da calculadora

  1. Declarar representantes para os eventos que o componente usará para comunicar valores para o formulário.

    Dica

    Embora você está declarando quatro eventos, você só precisa criar três representantes, porque dois eventos tenham a mesma assinatura.

    Logo abaixo das declarações de variáveis inseridas para a etapa anterior, digite o código a seguir:

    // This delegate will be invoked with two of your events.
    public delegate void FactorialCompleteHandler(double Factorial, double TotalCalculations);
    public delegate void AddTwoCompleteHandler(int Result, double TotalCalculations);
    public delegate void LoopCompleteHandler(double TotalCalculations, int Counter);
    
  2. Declarar eventos que seu componente usará para comunicar com seu aplicativo. Faça isso adicionando o seguinte código imediatamente abaixo do código inserido na etapa anterior.

    public event FactorialCompleteHandler FactorialComplete;
    public event FactorialCompleteHandler FactorialMinusOneComplete;
    public event AddTwoCompleteHandler AddTwoComplete;
    public event LoopCompleteHandler LoopComplete;
    
  3. Logo abaixo do código que você digitou na etapa anterior, digite o seguinte código:

    // This method will calculate the value of a number minus 1 factorial
    // (varFact2-1!).
    public void FactorialMinusOne()
    {
       double varTotalAsOfNow = 0;
       double varResult = 1;
       // Performs a factorial calculation on varFact2 - 1.
       for (int varX = 1; varX <= varFact2 - 1; varX++)
       {
          varResult *= varX;
          // Increments varTotalCalculations and keeps track of the current 
          // total as of this instant.
          varTotalCalculations += 1;
          varTotalAsOfNow = varTotalCalculations;
       }
       // Signals that the method has completed, and communicates the 
       // result and a value of total calculations performed up to this 
       // point.
       FactorialMinusOneComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will calculate the value of a number factorial.
    // (varFact1!)
    public void Factorial()
    {
       double varResult = 1;
       double varTotalAsOfNow = 0;
       for (int varX = 1; varX <= varFact1; varX++)
       {
          varResult *= varX;
          varTotalCalculations += 1;
          varTotalAsOfNow = varTotalCalculations;
       }
       FactorialComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will add two to a number (varAddTwo+2).
    public void AddTwo()
    {
       double varTotalAsOfNow = 0;  
       int varResult = varAddTwo + 2;
       varTotalCalculations += 1;
       varTotalAsOfNow = varTotalCalculations;
       AddTwoComplete(varResult, varTotalAsOfNow);
    }
    
    // This method will run a loop with a nested loop varLoopValue times.
    public void RunALoop()
    {
       int varX;
       double varTotalAsOfNow = 0;
       for (varX = 1; varX <= varLoopValue; varX++)
       {
        // This nested loop is added solely for the purpose of slowing down
        // the program and creating a processor-intensive application.
          for (int varY = 1; varY <= 500; varY++)
          {
             varTotalCalculations += 1;
             varTotalAsOfNow = varTotalCalculations;
          }
       }
       LoopComplete(varTotalAsOfNow, varLoopValue);
    }
    

Transferindo a entrada do usuário para o componente

A próxima etapa é adicionar código a frmCalculations para receber entrada do usuário e para transferir e receber valores de e o componente de Calculator .

Para implementar a funcionalidade front-end para frmCalculations

  1. frmCalculations aberto em editor de códigos.

  2. Localize a declaração de public partial class frmCalculations . Logo abaixo do tipo de { :

    Calculator Calculator1;
    
  3. Localize o construtor. Antes de }, adicione a seguinte linha:

    // Creates a new instance of Calculator.
    Calculator1 = new Calculator();
    
  4. Em o designer, clique em cada botão para gerar o contorno de código para manipuladores de eventos de Click de cada controle e para adicionar o código para criar os manipuladores.

    Quando concluída, os manipuladores de eventos de Click deve se parecer com o seguinte:

    // Passes the value typed in the txtValue to Calculator.varFact1.
    private void btnFactorial1_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact1 = int.Parse(txtValue.Text);
       // Disables the btnFactorial1 until this calculation is complete.
       btnFactorial1.Enabled = false;
       Calculator1.Factorial();
    }
    
    private void btnFactorial2_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact2 = int.Parse(txtValue.Text);
       btnFactorial2.Enabled = false;
       Calculator1.FactorialMinusOne();
    }
    private void btnAddTwo_Click(object sender, System.EventArgs e)
    {
       Calculator1.varAddTwo = int.Parse(txtValue.Text);
       btnAddTwo.Enabled = false;
       Calculator1.AddTwo();
    }
    private void btnRunLoops_Click(object sender, System.EventArgs e)
    {
       Calculator1.varLoopValue = int.Parse(txtValue.Text);
       btnRunLoops.Enabled = false;
       // Lets the user know that a loop is running
       lblRunLoops.Text = "Looping";
       Calculator1.RunALoop();
    }
    
  5. Após o código que você adicionou na etapa anterior, digite o seguinte para manipular eventos do formulário receberá Calculator1:

    private void FactorialHandler(double Value, double Calculations)
    // Displays the returned value in the appropriate label.
    {
       lblFactorial1.Text = Value.ToString();
       // Re-enables the button so it can be used again.
       btnFactorial1.Enabled = true;
       // Updates the label that displays the total calculations performed
       lblTotalCalculations.Text = "TotalCalculations are " + 
          Calculations.ToString();
    }
    
    private void FactorialMinusHandler(double Value, double Calculations)
    {
       lblFactorial2.Text = Value.ToString();
       btnFactorial2.Enabled = true;
       lblTotalCalculations.Text = "TotalCalculations are " + 
          Calculations.ToString();
    }
    
    private void AddTwoHandler(int Value, double Calculations)
    {
       lblAddTwo.Text = Value.ToString();
       btnAddTwo.Enabled = true;
       lblTotalCalculations.Text = "TotalCalculations are " +
          Calculations.ToString();
    }
    
    private void LoopDoneHandler(double Calculations, int Count)
    {
       btnRunLoops.Enabled = true;
       lblRunLoops.Text = Count.ToString();
       lblTotalCalculations.Text = "TotalCalculations are " +
          Calculations.ToString();
    }
    
  6. Em o construtor de frmCalculations, adicione o seguinte código imediatamente antes de } para manipular eventos personalizados que seu formulário receberá de Calculator1.

    Calculator1.FactorialComplete += new
       Calculator.FactorialCompleteHandler(this.FactorialHandler);
    Calculator1.FactorialMinusOneComplete += new
       Calculator.FactorialCompleteHandler(this.FactorialMinusHandler);
    Calculator1.AddTwoComplete += new
       Calculator.AddTwoCompleteHandler(this.AddTwoHandler);
    Calculator1.LoopComplete += new
       Calculator.LoopCompleteHandler(this.LoopDoneHandler);
    

Testando o aplicativo

Você criou um projeto que incorpora um formulário e um componente capazes de vários executar cálculos complexos. Embora você não implementa o recurso de multithreading ainda, você irá testar seu projeto para verificar sua funcionalidade antes de continuar.

Para testar o projeto

  1. Em o menu de Depurar , escolha Iniciar Depuração.

    O aplicativo é iniciado e frmCalculations aparece.

  2. Em a caixa de texto, digite 4, clique no botão rotulado Adicione dois.

    O número “6 " deve ser exibido no rótulo abaixo de ele, e “os cálculos total são 1 " devem ser exibidos em lblTotalCalculations.

  3. Agora clique no botão rotulado Factorial - 1.

    O número “6 " deve ser exibido abaixo do botão, e os cálculos gerais de leitura de lblTotalCalculations agora “é 4. "

  4. Altere o valor na caixa de texto a 20, clique no botão rotulado Fatorial.

    O número 2.43290200817664E+18 “” é exibido abaixo de ele, e lê “cálculos gerais de lblTotalCalculations agora é 24.”

  5. Altere o valor na caixa de texto a 50000, e clique no botão rotulado executar um loop.

    Observe que há um pequeno intervalo mas visível antes que esse botão novamente está ativado. O rótulo em esse botão deve ler “50000 " e os cálculos gerais são exibidos “25000024”.

  6. Altere o valor na caixa de texto a 5000000 e clique no botão rotulado executar um loop, então clique imediatamente o botão rotulado Adicione dois. Clique em novamente.

    O botão não responde, ou qualquer controle no formulário responderá até que os loops estejam concluídos.

    Se seu programa é apenas um único thread de execução, os cálculos de processamento intensivo como o exemplo anterior têm a tendência a anterior o programa até que os cálculos estejam concluídos. Em a próxima seção, você irá adicionar o recurso de multithreading ao seu aplicativo para que vários segmentos podem ser executados imediatamente.

Adicionando recurso de multithreading

O exemplo anterior demonstrou as limitações de aplicativos que executam somente um único thread de execução. Em a próxima seção você usará a classe de Thread para adicionar vários segmentos de execução para seu componente.

Para adicionar a sub-rotina de segmentos

  1. Calculator.cs Aberto em editor de códigos.

  2. Próximo à parte superior do código, localize a declaração de classe, e logo abaixo de {, digite o seguinte:

    // Declares the variables you will use to hold your thread objects.
    public System.Threading.Thread FactorialThread; 
    public System.Threading.Thread FactorialMinusOneThread;  
    public System.Threading.Thread AddTwoThread; 
    public System.Threading.Thread LoopThread;
    
  3. Imediatamente antes do final da declaração de classe na parte inferior de código, adicione o seguinte método:

    public void ChooseThreads(int threadNumber)
    {
    // Determines which thread to start based on the value it receives.
    switch(threadNumber)
       {
          case 1:
             // Sets the thread using the AddressOf the subroutine where
             // the thread will start.
             FactorialThread = new System.Threading.Thread(new
                System.Threading.ThreadStart(this.Factorial));
             // Starts the thread.
             FactorialThread.Start();
             break;
          case 2:
             FactorialMinusOneThread = new
                System.Threading.Thread(new
                   System.Threading.ThreadStart(this.FactorialMinusOne));
             FactorialMinusOneThread.Start();
             break;
          case 3:
             AddTwoThread = new System.Threading.Thread(new
                 System.Threading.ThreadStart(this.AddTwo));
             AddTwoThread.Start();
             break;
          case 4:
             LoopThread = new System.Threading.Thread(new
                System.Threading.ThreadStart(this.RunALoop));
             LoopThread.Start();
             break;
       }
    }
    

    Quando Thread é instanciado, requer um argumento na forma de ThreadStart. ThreadStart é um delegado que aponta para o endereço do método onde o segmento é iniciar. ThreadStart não pode ter parâmetros ou passar valores e pode, portanto somente indicar um método de void . O método de ChooseThreads que você implementou receberá apenas um valor de programa que chama o e usará esse valor para determinar o segmento iniciar apropriado.

Para adicionar o código apropriado para frmCalculations

  1. Abra o arquivo de frmCalculations.cs em editor de códigos, então localize private void btnFactorial1_Click.

    1. Comente a linha que chama o método de Calculator1.Factorial1 diretamente como mostrado:

      // Calculator1.Factorial()
      
    2. Adicione a seguinte linha para chamar o método de Calculator1.ChooseThreads :

      // Passes the value 1 to Calculator1, thus directing it to start the 
      // correct thread.
      Calculator1.ChooseThreads(1);
      
  2. Faça alterações semelhantes aos outros métodos de button_click .

    Dica

    Certeza que incluirá o valor apropriado para o argumento de Threads .

    Quando você terminar, o código deve ser semelhante ao seguinte:

    private void btnFactorial1_Click(object sender, System.EventArgs e)
    // Passes the value typed in the txtValue to Calculator.varFact1
    {
       Calculator1.varFact1 = int.Parse(txtValue.Text);
       // Disables the btnFactorial1 until this calculation is complete
       btnFactorial1.Enabled = false;
       // Calculator1.Factorial();
       Calculator1.ChooseThreads(1);
    }
    
    private void btnFactorial2_Click(object sender, System.EventArgs e)
    {
       Calculator1.varFact2 = int.Parse(txtValue.Text); 
       btnFactorial2.Enabled = false;         
       // Calculator1.FactorialMinusOne();
       Calculator1.ChooseThreads(2);
    }
    private void btnAddTwo_Click(object sender, System.EventArgs e)
    {
       Calculator1.varAddTwo = int.Parse(txtValue.Text);
       btnAddTwo.Enabled = false;
       // Calculator1.AddTwo();
       Calculator1.ChooseThreads(3);
    }
    
    private void btnRunLoops_Click(object sender, System.EventArgs e)
    {
       Calculator1.varLoopValue = int.Parse(txtValue.Text);
       btnRunLoops.Enabled = false;
       // Lets the user know that a loop is running
       lblRunLoops.Text = "Looping";
       // Calculator1.RunALoop();
       Calculator1.ChooseThreads(4);
    }
    

Chamadas empacotamento a controles

Você agora facilitará atualizar a exibição no formulário. Como controles são sempre pertencentes pelo segmento principal de execução, qualquer chamada a um controle de um segmento subordinado requer uma chamada empacotamento . Empacotamento é o ato de mover uma chamada através dos limites de segmento, e é muito caro em termos de recursos. Para minimizar a quantidade de empacotamento que precisa ocorrer, e para garantir que seus chamadas são tratados de maneira segura, você usará o método de Control.BeginInvoke para chamar métodos no segmento principal de execução, minimizando de essa maneira a quantidade de empacotamento de cruz-thread- limite que deve ocorrer. Esse tipo de chamada é necessário para chamar os métodos que manipulam controles. Para obter detalhes, consulte:Como manipular controles a partir de threads.

Para criar os procedimentos de controle chamando

  1. Abrir o editor de códigos para frmCalculations. Em as declarações, a seção consulte adicione o seguinte código:

    public delegate void FHandler(double Value, double Calculations);
    public delegate void A2Handler(int Value, double Calculations);
    public delegate void LDHandler(double Calculations, int Count);
    

    Invoke e BeginInvoke requerem um representante para o método apropriado como um argumento. Essas linhas declaram as assinaturas de representante que serão usadas por BeginInvoke para chamar os métodos apropriados.

  2. Adicione os seguintes métodos vazios ao seu código.

    public void FactHandler(double Value, double Calculations)
    {
    }
    public void Fact1Handler(double Value, double Calculations)
    {
    }
    public void Add2Handler(int Value, double Calculations)
    {
    }
    public void LDoneHandler(double Calculations, int Count)
    {
    }
    
  3. Em o menu de Editar , use Recortar e Colar para recortar todo o código do método FactorialHandlere a colagem em FactHandler.

  4. Repita a etapa anterior para FactorialMinusHandler e Fact1Handler, AddTwoHandler e Add2Handler, e LoopDoneHandler e LDoneHandler.

    Quando concluir, não deve haver qualquer código que permanece em FactorialHandler, em Factorial1Handler, em AddTwoHandler, e em LoopDoneHandler, e qualquer código esses usados para conter deve ter sido movido para novos métodos apropriados.

  5. Chame o método de BeginInvoke para chamar os métodos de forma assíncrona. Você pode chamar BeginInvoke do formulário (this) ou qualquer dos controles no formulário.

    Quando concluída, o código deve ser semelhante ao seguinte:

    protected void FactorialHandler(double Value, double Calculations)
    {
       // BeginInvoke causes asynchronous execution to begin at the address
       // specified by the delegate. Simply put, it transfers execution of 
       // this method back to the main thread. Any parameters required by 
       // the method contained at the delegate are wrapped in an object and 
       // passed. 
       this.BeginInvoke(new FHandler(FactHandler), new Object[]
          {Value, Calculations});
    }
    protected void FactorialMinusHandler(double Value, double Calculations)
    {
       this.BeginInvoke(new FHandler(Fact1Handler), new Object []
          {Value, Calculations});
    }
    
    protected void AddTwoHandler(int Value, double Calculations)
    {
       this.BeginInvoke(new A2Handler(Add2Handler), new Object[]
          {Value, Calculations});
    }
    
    protected void LoopDoneHandler(double Calculations, int Count)
    {
       this.BeginInvoke(new LDHandler(LDoneHandler), new Object[]
          {Calculations, Count});
    }
    

    Pode parecer se o manipulador de eventos está fazendo simplesmente uma chamada para o método a seguir. O manipulador de eventos está causando realmente um método a ser chamado no segmento principal da operação. Essa abordagem salva em chamadas através dos limites de segmento e permite seus aplicativos multissegmentados executar com e sem causar medo do aprisionamento. Para obter detalhes sobre como trabalhar com controles em um ambiente de vários segmentos, consulte Como manipular controles a partir de threads.

  6. Salvar seu trabalho.

  7. Testar sua solução Iniciar Depuração escolhendo no menu de Depurar .

    1. Digite 10000000 na caixa de texto e clique executar um loop.

      “Loop” é exibido no rótulo abaixo de esse botão. Este loop deve levar uma quantidade significativa de tempo para executar. Se for concluída muito cedo, ajuste o tamanho do número de acordo.

    2. Em sucessão rápida, clique em todos os três botões que estão habilitados ainda. Você verá que todos os botões respondem a sua entrada. O rótulo abaixo de Adicione dois deve ser a primeira para exibir um resultado. Os resultados serão exibidos posteriormente nos rótulos abaixo dos botões factorial. Esses resultados avaliada como infinito, como o número retornado por um 10.000.000 factorial é muito grande para que uma variável de precisão dupla contém. Finalmente, após um atraso adicional, resultados é retornado abaixo do botão de executar um loop .

      Como você observou apenas, quatro conjuntos separados de cálculos foram executados simultaneamente em cima de quatro segmentos separados. A interface do usuário permanece responde a entrada, e os resultados são retornados após cada thread foi concluída.

Coordenando seus segmentos

Um usuário experiente de aplicativos de vários segmentos pode perceber uma falha sutil com o código como digitado. Lembre as linhas de código de cada método cálculo- executando em Calculator:

varTotalCalculations += 1;
varTotalAsOfNow = varTotalCalculations;

Essas duas linhas de código incrementam varTotalCalculations variável pública e definem a variável local varTotalAsOfNow para este valor. Esse valor é retornado na frmCalculations e exibido em um controle do rótulo. Mas o valor correto está sendo retornado? Se apenas um único thread de execução está sendo executado, a resposta é claramente sim. Mas se vários segmentos estão executando o, a resposta torna-se mais incerta. Cada segmento tem a capacidade de incremento varTotalCalculationsvariável. É possível que depois que um segmento incrementa essa variável, mas antes que copia o valor a varTotalAsOfNow, outro segmento pode alterar o valor de essa variável incrementando o. Isso resulta na possibilidade que cada segmento, na verdade, está relatando resultados imprecisas. Visual C# fornece Instrução lock (Referência de C#) para permitir que a sincronização de threads certifique-se de que cada segmento sempre retorna um resultado preciso. A sintaxe para lock é o seguinte:

lock(AnObject)
{
   // Insert code that affects the object.
   // Insert more code that affects the object.
   // Insert more code that affects the object.
// Release the lock.
}

Quando o bloco de lock for inserida, a execução na expressão especificada será bloqueada até que o segmento especificado tem um bloqueio exclusivo no objeto em questão. Em o exemplo mostrado acima, a execução é bloqueada em AnObject. lock deve ser usado com um objeto que retorna uma referência em vez de um valor. A execução pode prosseguir como um bloco para sem interferência de outros segmentos. Um conjunto de declarações que executam como uma unidade. seria atomic Quando } é localizado, a expressão é liberado e os segmentos são permitidos normalmente continuar.

Para adicionar a declaração de bloqueio para seu aplicativo

  1. Calculator.cs Aberto em editor de códigos.

  2. Localize cada instância de código a seguir:

    varTotalCalculations += 1;
    varTotalAsOfNow = varTotalCalculations;
    

    Deve haver quatro instâncias de esse código, uma em cada método calculate.

  3. Modifique este código para que ele leia o seguinte:

    lock(this)
    {
       varTotalCalculations += 1;
       varTotalAsOfNow = varTotalCalculations;
    }
    
  4. Salve seu trabalho e testá-lo como no exemplo anterior.

    Você pode observar um leve impacto no desempenho do seu programa. Isso ocorre porque a execução de segmentos para quando um bloqueio exclusivo é obtido no seu componente. Embora verifique se a precisão, essa abordagem impede alguns benefícios de desempenho de vários segmentos. Você deve considerar com cuidado a necessidade para bloquear segmentos, e implementá-los com somente quando necessário.

Consulte também

Tarefas

Como coordenar vários threads de execução

Instruções passo a passo: criando um componente multithreaded simples com o Visual Basic

Referência

BackgroundWorker

Conceitos

Visão geral do padrão assíncrono baseado em evento

Outros recursos

Programando com componentes

Instruções passo a passo para programação de componentes

Multithreading em componentes