Solucionado (ver solução)
Solucionado
(ver solução)
10
respostas

Utilizar ou não Async await

Em paralelo ao curso de paralelismo estou tentando desenvolver um processo que realiza a coleta de alguns contadores de impressoras via protocolo SNMP, pensei em utilizar Threads para realizar este processo, refatorei para utilizar tasks como dito no curso, porém não sei se deveria utilizar um async await para esta tarefa, neste caso me pego meio confuso se estou desenvolvendo este método da melhor maneira. Segue código do método:

private void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            using (ImpressoraDAO dao = new ImpressoraDAO())
            {
                var impressoras = dao.Listar().ToList();
                var impressorasTasks = impressoras.Select(impressora =>
                {
                    return Task.Factory.StartNew(() =>
                    {
                        if (impressora.IsConected())
                        {
                            using (ContadorDAO contadorDao = new ContadorDAO())
                            {
                                var contador = new Contador();
                                contador.Id = DateTime.Now;
                                contador.ImpressoraId = impressora.Id;
                                contador.QuantidadePaginas = Convert.ToInt64(OperacaoSNMP.ObterObjetoOID(IPAddress.Parse(impressora.Ip), OID.CONTADADOR_TOTAL));
                                contadorDao.Adicionar(contador);
                            }
                        }
                    });
                }).ToArray();

                Task.WhenAll(impressorasTasks);
            }
        }

Como posso melhorar este código ?

10 respostas

Olá Lucas,

o uso de um async await aqui ficaria mais interessante se neste processo você dependesse do retorno das taks e/ou existisse alguma outra lógica que dependesse do término destas tarefas. O que parece não ser nenhum dos outros casos pelo código que você mandou acima dado que após o WhenAll nenhuma outra tarefa é desempenhada.

Uma possível melhoria, mas até que pequena, que eu faria no código seria a lógica de criação do contador. Esta lógica de criar um novo contador a partir do id da impressora de a quantidade de páginas em cima de uma lógica a partir do impressora.Ip poderiam estar num construtor de Contador que recebe estas informações e faz todas esta lógica.

E caso você esteja trabalhando com algum sistema de injeção de dependências, daria para retirar também a criação dos Daos de dentro deste método.

Primeiramente muito obrigado pelo seu feedback Lucas! Irei refatorar a logica de obtenção do contador utilizando o Ip da impressora no construtor do contador. Em relação ao contadorDAO, ele é somente uma camada entre a regra de negócio e o Entity Framework Core que utilizo. uma duvida que surgiu é que essa operação que estou realizando as vezes gera um timeoutException, só que nao consegui imaginar como capturar essa exceção entre as tasks, gostaria que me informasse qual caminho seguir quanto a isto, já que mais a frente pretendo gerar um log a partir da captura dessas exceções!

Aqui tem um link da microsoft mais aprofundado sobre como tratar exceptions com task. A ideia é que você consegue colocar o WaitAll envolta de um try/catch para capturar eventuais exceptions que ocorreram dentro da task.

Mesmo Com a idéia de capturar logs posteriormente, ainda seria então, melhor deixar síncrono como está?

Parece que sim, dado que você gerará logs a partir das exceptions, ou seja, este log estará fora da parte assíncrona. Este log seria executado caso uma das task desse erro e escreveria sobre este erro.

Olá Lucas, estou aqui para mostrar o que fiz até agora, preferi usar Tasks assíncronas, espero que esteja correto o código, ainda irei melhorar a lógica do construtor do contador como me sugeriu, até o momento cheguei ao seguinte código:

private async void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            using (ImpressoraDAO impressoraDAO = new ImpressoraDAO())
            using (ContadorDAO contadorDAO = new ContadorDAO())
            {
                var impressoras = impressoraDAO.Listar();

                try
                {
                    var contadores = await GetListaTasksContadorAsync(impressoras);

                    await Task.WhenAll(contadores);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }
            }
        }

        private async Task<Task[]> GetListaTasksContadorAsync(IReadOnlyCollection<Impressora> impressoras)
        {
            var contadores = new List<Contador>();

            var tasks = impressoras.Select(impressora =>
               Task.Factory.StartNew(() => GetContadorAsync(impressora))
            );

            return await Task.WhenAll(tasks);
        }

        private async Task GetContadorAsync(Impressora impressora)
        {
            if (impressora.IsConected())
            {
                try
                {
                    using (ContadorDAO contadorDAO = new ContadorDAO())
                    {
                        Contador contador = new Contador();
                        contador.QuantidadePaginas = await OperadorSNMP.ObterContador(impressora);
                        contador.Id = DateTime.Now;
                        contador.ImpressoraId = impressora.Id;
                        await contadorDAO.AdicionarAsync(contador);
                    }
                }
                catch (Exception)
                {
                    throw;
                }
            }
        }

Olá Lucas,

tem algumas coisas que dá para você apagar no código que não estão sendo usadas.

Em GetListaTasksContadorAsync note que você acaba não usando o var contadores = new List<Contador>(); agora que está lidando com o async await. Você pode apagar esta variável.

Já no método timer_Elapsed você não usa o ContadorDAO neste método específico. Então daria para tirá-lo daí também. Outra coisa neste método é que quando ele pede a execução do GetListaTasksContadorAsync, ele já faz o Task.WhenAll. Então no timer_Elapsed, no momento do

var contadores = await GetListaTasksContadorAsync(impressoras);
await Task.WhenAll(contadores);

O Task.WhenAll acaba só repetindo o que o GetListaTasksContadorAsync já faz, então é meio desnecessário também.

Um último ponto que poderia melhorar é no GetContadorAsync fazer alguma lógica no catch que está vazio por enquanto.

solução!

Lucas, bom dia, dei mais uma mexida no código e o deixei da seguinte maneira:

public class CountService
    {
        private Timer _timer;

        public CountService()
        {
            _timer = new Timer(30000);
            _timer.Elapsed += timer_Elapsed;
        }

        private async void timer_Elapsed(object sender, ElapsedEventArgs e)
        {
            using (ImpressoraDAO impressoraDAO = new ImpressoraDAO())
            {
                var impressoras = impressoraDAO.Listar();

                var tasks = impressoras.Select(impressora =>
                      Task.Factory.StartNew(() => GetContadorAsync(impressora))
                );

                try
                {
                    await Task.WhenAll(tasks);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

            }
        }

        private async Task GetContadorAsync(Impressora impressora)
        {
            if (impressora.IsConected())
            {
                using (ContadorDAO contadorDAO = new ContadorDAO())
                {
                    Contador contador = new Contador();
                    contador.QuantidadePaginas = await OperadorSNMP.ObterContadorAsync(impressora);
                    contador.Id = DateTime.Now;
                    contador.ImpressoraId = impressora.Id;
                    await contadorDAO.AdicionarAsync(contador);
                }
            }
        }

        public void Start()
        {
            _timer.Start();
        }

        public void Stop()
        {
            _timer.Stop();
        }
    }

Hoje minha maior duvida é de como capturar as exceções, estudei os links que você me proporcionou, vi que alguns tipos de uso das tasks geram uma AggregateException, porém dei uma lida em outros artigos e alguns disseram que o Task.WhenAll retorna somente a ultima exceção capturada, ainda tenho muita dificuldade com isso, e já não sei qual a melhor maneira de tratar essas exceções, poderia me auxiliar mais uma vez? Caso ache necessário a abertura de outro tópico ou que o assunto não é mais pertinente, me avise e encerrarei o tópico.

Olá Lucas,

se entendi bem, o que você quer é tratar as exceptions individualmente para cada uma das tasks. Este link é de uma dúvida que eu encontrei nesta linha, em que o cara queria tratar a exception para cada task individualmente, imprimindo um log de erros se ocorrer uma exception no meio da task.

E também encontrei também este link que explica um pouco melhor o comportamento das exceptions quando usamos o await. Na verdade, o que ocorre é que ao invés de lançar uma AggregateException, ao usar o comando await é lançada a exception específica que uma das tasks disparou.

Lucas, desculpa a demora para dar um feedback, gostaria de agradecer a sua atenção e dizer que um dos links que proporcionou, mais claramente o primeiro, me levou à solução que eu almejava. Muito obrigado pela sua prestatividade e pelo suporte!