Solucionado (ver solução)
Solucionado
(ver solução)
1
resposta

resumo explicativo da task com retorno

O código abaixo é do curso "C#: Paralelismo no mundo real" aula 03.

Eu comentei cada linha do código dos métodos 'btnProcessar_click' e o que o professor criou 'ConsolidarContas' para poder ver se realmente peguei toda a explicação de maneira correta.

Gostaria que me confirmassem se os comentários que fiz sobre o que faz cada linha bate realmente com a execução do aplicativo.

A aula foi muito interessante, porém não me senti com o assunto consolidado.

Agradeço desde já.



private void AtualizarView(List<String> result, TimeSpan elapsedTime)
        {
            var tempoDecorrido = $"{ elapsedTime.Seconds }.{ elapsedTime.Milliseconds} segundos!";
            var mensagem = $"Processamento de {result.Count} clientes em {tempoDecorrido}";

            LstResultados.ItemsSource = result;
            TxtTempo.Text = mensagem;
        }

        private Task<List<string>> ConsolidarContas(IEnumerable<ContaCliente> contas)
        {
            //variavel que vai guardar a lista que vai ser mostrada
            var resultado = new List<string>();

            //executa uma task que vai processando e guardando na variavel 'resultado'
            //e guarda dentro da lista de tasks 'contasTarefas' todas as tasks que o .NET está usando
            var contasTarefas = contas.Select(conta =>
            {
                return Task.Factory.StartNew(() =>
                {
                    var resultadoConta = r_Servico.ConsolidarMovimentacao(conta);
                    resultado.Add(resultadoConta);
                });
            }).ToArray();

            //espera o 'contasTarefas' parar, para executar o que tem dentro do 'ContinueWith'
            //que no caso é pra retornar a 'list<string>'
            //assim que retornar 'resultado', consequentemente retorna a task que contem 'resultado'
            return Task.WhenAll(contasTarefas).ContinueWith(t =>
            {
                return resultado;
            });
        }

        private void BtnProcessar_Click(object sender, RoutedEventArgs e)
        {
            //pega o taskScheduler principal, no caso o GUI
            var taskSchedulerUI = TaskScheduler.FromCurrentSynchronizationContext();
            //bloqueia o 'btnProcessar' para não receber chamadas enquanto o processamento continua
            BtnProcessar.IsEnabled = false;
            //recebe todas as contas do repositório
            var contas = r_Repositorio.GetContaClientes();
            //atualiza os dados da tela
            AtualizarView(new List<string>(), TimeSpan.Zero);
            //guarda o tempo inicial
            var inicio = DateTime.Now;
            //
            ConsolidarContas(contas).ContinueWith(task => {
                //guarda o tempo final
                var fim = DateTime.Now;
                //cria uma variavel que guarda o retorno da task, que no caso é a 'list<string>'
                var resultado = task.Result;
                //atualiza os dados da tela
                AtualizarView(resultado, fim - inicio);
            }, taskSchedulerUI).ContinueWith(task => {
                //graças ao 'taskSchedulerUI' acima, o task é executado dentro da thread GUI
                //assim que executar o 'ConsolidarContas(contas)' , graças ao 'ContinueWith' o aplicativo
                //rodará esta outra task.
                //graças ao 'taskSchedulerUI' abaixo, o task é executado dentro da thread GUI
                BtnProcessar.IsEnabled = true;
            }, taskSchedulerUI);
        }
1 resposta
solução!

Olá, Leônio. Tudo bom?

Suas anotações estão ótimas!

Eu só faria algumas coisas diferentes, no método BtnProcessar_Click:

//pega o taskScheduler principal, no caso o GUI
var taskSchedulerUI = TaskScheduler.FromCurrentSynchronizationContext();
...

O método estático FromCurrentSynchronizationContext da classe TaskScheduler não retorna, necessariamente, o TaskScheduler da GUI. Na verdade, retorna o TaskScheduler da thread atual. No caso, sabemos que é da GUI, porque, estamos dentro do método BtnProcessar_Click que é executado no evento Click do botão. Mas, se usarmos este método estático dentro de uma Task, por exemplo, poderemos ter um TaskScheduler diferente... Ou não! Quem decidirá isto é o sistema de Tasks do .NET.

...
}, taskSchedulerUI).ContinueWith(task => {
    //graças ao 'taskSchedulerUI' acima, o task é executado dentro da thread GUI
    //assim que executar o 'ConsolidarContas(contas)' , graças ao 'ContinueWith' o aplicativo
    //rodará esta outra task.
    //graças ao 'taskSchedulerUI' abaixo, o task é executado dentro da thread GUI
    BtnProcessar.IsEnabled = true;
}, taskSchedulerUI);

Não entendi muito bem a parte:

//assim que executar o 'ConsolidarContas(contas)' , graças ao 'ContinueWith' o aplicativo
//rodará esta outra task.

Mas, o ConsolidarContas é uma Task. Quando escrevemos ConsolidarContas(contas).ContinueWith estamos usando o retorno de seu método, que é:

Task.WhenAll(contasTarefas).ContinueWith(t =>
{
    return resultado;
});

Então, juntando as coisas, temos:

Task.WhenAll(contasTarefas).ContinueWith(t => {
    return resultado;
}).ContinueWith(task =>
    // Vamos esperar a task anterior terminar e somente quando a task anterior terminar
    // vamos executar o código no bloco de chaves abaixo. Mas, eu não preciso saber qual
    // TaskScheduler foi usado na task anterior!
    {
        var fim = DateTime.Now;
        var resultado = task.Result;
        AtualizarView(resultado, fim - inicio);
    }, taskSchedulerUI // Opa, o argumento 'taskSchedulerUI' me diz que esta task deve
                       // ser executada exatamente neste TaskScheduler que será o mesmo
                       // da GUI quando o método for executado pelo evento de Click do
                       // botão.
).ContinueWith(task =>
    // Aqui eu também não sei e não me importo com o contexto utilizado na task anterior,
    // mas só sei que vou esperar ela terminar para executar meu código.
    {
        BtnProcessar.IsEnabled = true;
    }
    , taskSchedulerUI // Mais uma vez definimos o TaskScheduler a ser usado. No caso, é
                      // novamente a 'taskSchedulerUI', mas poderia ser outro!
);

Talvez eu tenha chovido no molhado, mas só pra confirmar que essa parte ficou clara! :)

Tudo bem? Queremos que seu conhecimento sobre isto esteja afiadíssimo, então, qualquer coisa, manda aqui no fórum que a gente responde!