C# - Modelos de código para cenários complexos


  Hoje vou apresentar três trechos de códigos que podemos usar em cenários mais complexos.

No dia a dia sempre vai surgir a necessidade de aplicar um código para resolver um cenário mais complexo, e muitas vezes esse cenário customa se repetir.

Desta forma vou mostrar três trechos de códigos que você pode usar para aplicar nestes cenários.

Cenário 1 - Multithreading com Paralelismo de tarefas (Task)

O multithreading pode melhorar muito o desempenho dos aplicativos. Utilizando a classe Task do namespace System.Threading.Tasks, você pode obter uma execução paralela eficiente.

A classe Task no namespace System.Threading.Tasks é uma parte importante da API de Tarefas usada para criar e gerenciar tarefas assíncronas. Ela é frequentemente utilizada para obter execuções paralelas eficientes em cenários de programação multithreading.

Aqui está um exemplo de uso de tarefas para processamento paralelo:

1- Modelo

Task<int> task1 = Task.Run(() => CalcularValor());
Task<
int> task2 = Task.Run(() => CalcularOutroValor());

await Task.WhenAll(task1, task2);

int resultado1 = task1.Result;
int
resultado2 = task2.Result;

Console.WriteLine($"Resultado 1: {resultado1}, Resultado 2: {resultado2}");

static int CalcularValor()
{
  
/* Implementação */
}

static int CalcularOutroValor()
{
  
/* Implementação */
}

 await Task.WhenAll(task1, task2);

O método WhenAll da classe Task aceita uma coleção de tarefas (ou parâmetros separados por vírgula) e retorna uma nova tarefa que representa a conclusão de todas as tarefas fornecidas.

Este código faz com que o programa aguarde a conclusão das tarefas task1 e task2 antes de prosseguir para as próximas instruções. Isso é importante quando você deseja garantir que todas as tarefas tenham terminado antes de prosseguir com o processamento dos resultados ou tomar outras ações que dependem do resultado dessas tarefas.

2- Implementação

Task<long> task1 = Task.Run(() => CalcularFibonacci(10));
Task<long> task2 = Task.Run(() => CalcularFatorial(5));
await Task.WhenAll(task1, task2);
long resultado1 = task1.Result;
long resultado2 = task2.Result;
Console.WriteLine($"Resultado Fibonacci: {resultado1}, Resultado Fatorial: {resultado2}");
static long CalcularFibonacci(int n)
{
    if (n <= 0)
        return 0;
    if (n == 1)
        return 1;
    long a = 0;
    long b = 1;
    long result = 0;
    for (int i = 2; i <= n; i++)
    {
        result = a + b;
        a = b;
        b = result;
    }
    return result;
}
static long CalcularFatorial(int n)
{
    if (n == 0)
        return 1;
    if (n < 0)
        throw new ArgumentException("O fatorial não está definido para números negativos.");
    long resultado = 1;
    for (int i = 1; i <= n; i++)
    {
        resultado *= i;
    }
    return resultado;
}

Neste exemplo, CalcularFibonacci calcula o n-ésimo número de Fibonacci e CalcularFatorial calcula o fatorial de um número. Ambos são tarefas práticas que podem ser executadas em paralelo usando a classe Task. O resultado dessas tarefas é esperado após o await com Task.WhenAll, e os valores calculados são impressos na saída.

Cenário 2 - Acesso a arquivos de forma Assíncrona

Tratar com operações assíncronas de arquivos é essencial para aplicativos responsivos. As palavras-chave async e await simplificam a programação assíncrona e os recursos StreamReader, ReadFileAsync() e ReadToEndAsync() permite fazer o acesso assíncrono.

1- Modelo

string caminhoArquivo = "data.txt";

using (StreamReader reader = new StreamReader(caminhoArquivo))
{
  
string conteudo = await reader.ReadToEndAsync();
   Console.WriteLine(conteudo);
}

StreamReader é uma classe no namespace System.IO que é usada para leitura de fluxos de caracteres de forma eficiente, principalmente em operações de leitura de arquivos de texto. Ele oferece métodos para ler dados de um Stream de forma assíncrona ou síncrona. Alguns de seus métodos comuns incluem:

- Read(): Lê o próximo caractere a partir do fluxo.
- ReadLine(): Lê a próxima linha de texto do fluxo.
- ReadToEnd(): Lê o restante do fluxo até o final.


StreamReader é especialmente útil quando você deseja ler um arquivo de texto de forma eficiente, pois ele faz buffering automático, minimizando as operações de leitura no disco.

ReadFileAsync é um método assíncrono personalizado que lê o conteúdo de um arquivo de forma assíncrona usando StreamReader. Ele aceita um caminho de arquivo como argumento e retorna uma tarefa que representa a operação de leitura assíncrona.

ReadToEndAsync é um método da classe StreamReader que lê o restante do fluxo de caracteres até o final de forma assíncrona. Ele retorna uma tarefa que representa a operação de leitura assíncrona. O uso do await com ReadToEndAsync permite que a leitura do arquivo seja realizada de forma assíncrona, evitando o bloqueio da thread principal.

2- Implementação

string caminhoArquivo = "exemplo.txt"; // O caminho para o arquivo

try
{
  
// Ler o conteúdo do arquivo de forma assíncrona
  
string conteudo = await ReadFileAsync(caminhoArquivo);

   // Processar o conteúdo lido, por exemplo, exibir na tela
   Console.WriteLine(
"Conteúdo do arquivo:");
   Console.WriteLine(conteudo);
}

catch
(Exception ex)
{
   Console.WriteLine(
$"Ocorreu um erro ao ler o arquivo: {ex.Message}");
}

Console.ReadKey();

static async Task<string> ReadFileAsync(string caminhoArquivo)
{
 
using (StreamReader reader = new StreamReader(caminhoArquivo))
  {
    
return await reader.ReadToEndAsync();
  }
}

Entendendo o código:

  1. Importamos os namespaces necessários.
  2. No método Main, lemos o conteúdo do arquivo "exemplo.txt" de forma assíncrona.
  3. O método ReadFileAsync faz a leitura do arquivo de forma assíncrona usando await reader.ReadToEndAsync(). Isso permite que o programa continue a executar outras tarefas enquanto o arquivo é lido, mantendo o aplicativo responsivo.
  4. Qualquer exceção que possa ocorrer durante a leitura do arquivo é tratada no bloco try-catch para fornecer um tratamento adequado.

Cenário 3 - Tratamento de exceções customizadas

A criação de exceções personalizadas permite lidar com casos de erros específicos com mais eficiência. Além disso as exceções personalizadas são importantes por vários motivos, incluindo:

  1. Melhor Compreensão do Código: Exceções personalizadas tornam o código mais legível e compreensível. Quando você lança ou captura uma exceção personalizada, está comunicando claramente a natureza do erro e o que aconteceu, tornando o código mais fácil de entender para você e para outros desenvolvedores.
     
  2. Melhor Depuração: Exceções personalizadas ajudam na depuração, pois você pode adicionar informações adicionais, como mensagens de erro específicas, dados de contexto ou até mesmo anexar propriedades personalizadas à exceção, tornando mais fácil identificar e corrigir problemas.
     
  3. Separação de Preocupações: Ao criar exceções personalizadas, você pode separar as preocupações do seu código. Isso significa que você pode criar exceções personalizadas que estão relacionadas a um domínio específico ou a uma parte específica do seu código, tornando-o mais organizado e manutenível.

1- Modelo

try
{
 
int result = RealizarOperacaoComplexa();
 
if (result < 0)
  {
   
throw new CustomException("Um resultado negativo não é permitido.");
  }
}

catch
(CustomException ex)
{
   Console.WriteLine(
$"Exceção Customizada: {ex.Message}");
}

static int RealizarOperacaoComplexa()
{
 
/* Implementação */
}

class CustomException : Exception
{
 
public CustomException(string message) : base(message) { }
}

A seguir temos um exemplo de definição e uso de uma exceção personalizada onde criaremos uma classe de exceção personalizada chamada CustomDataException, que incluirá informações adicionais, como a hora em que ocorreu a exceção e os dados relacionados ao erro.

Este é um exemplo mais complexo para ilustrar como você pode criar exceções personalizadas mais ricas em informações:

using System;
using System.Runtime.Serialization;
[Serializable]
class CustomDataException : Exception
{
    public DateTime Timestamp { get; }
    public string ErrorData { get; }
    public CustomDataException(string message, string errorData) : base(message)
    {
        Timestamp = DateTime.Now;
        ErrorData = errorData;
    }
    public CustomDataException(string message, string errorData, Exception innerException) 
                                           : base(message, innerException)
    {
        Timestamp = DateTime.Now;
        ErrorData = errorData;
    }
    protected CustomDataException(SerializationInfo info, StreamingContext context) : base(info, context)
    {
        Timestamp = info.GetDateTime("Timestamp");
        ErrorData = info.GetString("ErrorData");
    }
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        base.GetObjectData(info, context);
        info.AddValue("Timestamp", Timestamp);
        info.AddValue("ErrorData", ErrorData);
    }
}
class Program
{
    static void Main(string[] args)
    {
        try
        {
            SimularOperacaoComErro();
        }
        catch (CustomDataException ex)
        {
            Console.WriteLine($"Custom Data Exception: {ex.Message}");
            Console.WriteLine($"Timestamp: {ex.Timestamp}");
            Console.WriteLine($"Error Data: {ex.ErrorData}");
        }
    }
    static void SimularOperacaoComErro()
    {
        try
        {
            // Simulação de uma operação com erro
            throw new CustomDataException("Ocorreu um erro durante a operação.", "Dados de erro específicos");
        }
        catch (Exception ex)
        {
            // Captura e relança a exceção
            throw new CustomDataException("Erro na operação interna.", "Dados internos de erro", ex);
        }
    }
}

Entendendo o código:

Este exemplo ilustra como criar uma exceção personalizada que pode conter informações adicionais para facilitar a depuração e o tratamento de erros complexos.

Pegue o código aqui: CodigosCenarios.zip

E  estamos conversados...

"Porque o amor ao dinheiro é a raiz de todos os males; e nessa cobiça alguns se desviaram da fé, e se traspassaram a si mesmos com muitas dores"
1 Timóteo 6:10

Referências:


José Carlos Macoratti