none
Reportar Progreso con Task Async RRS feed

  • Pregunta


  • Buenas tardes a todos, estoy aprendiendo a crear aplicaciones asincronas utilizando el patron Task Async tras leer un poco sobre el tema y veo que para reportar el progreso de varias tareas se puede utilizar el la interfaz IProgress<T> de la siguiente manera.

     public async void Download()
            {
                await DownloadToFileASync(new Uri("http://ejemplo.com/algunaImagen.png"), "C:\\imagenes\\algunaImagen.png",
                        new Progress<int>(
                            i =>
                            {
                                Console.WriteLine($"Descargando Imagen {i}% completado.");
                            }));
    
                HacerOtrasCosasCuandoLaDescargaTermine();
            }
    
            public async Task DownloadToFileASync(Uri address,string destination, IProgress<int> progressReport)
            {
                using (var client = new HttpClient())
                {
                    var responseMessage = await client.GetAsync(address, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
                    if (!responseMessage.IsSuccessStatusCode)
                        throw new Exception("Ocurrío un error al obtener respuesta del servidor.");
    
                    var totalSize = responseMessage.Content.Headers.ContentLength.GetValueOrDefault();
                    var bytesReceivedCount = 0L;
                    var buffer = new byte[1024];
    
                    using (Stream stream = await responseMessage.Content.ReadAsStreamAsync().ConfigureAwait(false))
                    using (var fileStream = new FileStream(destination, FileMode.CreateNew))
                    {
                        long readedBytes;
                        do
                        {
                            readedBytes = stream.Read(buffer, 0, buffer.Length);
                            bytesReceivedCount += readedBytes;
                            await fileStream.WriteAsync(buffer, 0, buffer.Length).ConfigureAwait(false);
                            if (totalSize > 0)
                            {
                                var currentProgress = bytesReceivedCount * 100 / totalSize;
                                progressReport?.Report((int)currentProgress);
                            }
                        } while (readedBytes != 0);
                    }
                }
            }

    Hasta aquí no tengo ningun problema el delegado se dispara cada que descargo una imagen y en consecuencia actualizo mi UI. Pero supongamos en el siguiente caso:

    public async void DownloadMany(Uri[] uris)
            {
                var taskList = new List<Task>();
    
                for (int i = 0; i < uris.Length; i++)
                {
                    taskList.Add(DownloadToFileASync(new Uri("http://ejemplo.com/algunaImagen.png"), $"C:\\imagenes\\imagen{1}.png",
                        new Progress<int>(p =>{Console.WriteLine($"Descargando Imagen {p}% completado.");})));
                }
                Task.WaitAll(taskList.ToArray());
            }

    Como podría llevar el seguimiento del progreso cuando deseo por ejemplo realizar 10 descargas concurrentes. Por ejemplo para actualizar algún objeto en la UI, normalmente con el enfoque orientado a eventos enviaría un id o incluso el mismo objeto como "userState" para poder llevar el seguimiento de que tarea se ejecuta en que momento, sin embargo esto no me parece del todo correcto ya que si implementamos el patrón completo obtendriamos metodos con muchos parametros por ejemplo:

    public void DownloadToFile(Uri address, string destination, IProgress<int> progressRepot, CancellationToken cancellation, object userState)
    De antemano agradesco la ayuda ideas que me puedan dar.

    martes, 1 de mayo de 2018 21:33

Todas las respuestas

  • Acaso no podría ser un Tuple<int, string> en lugar de un <int>

    Imagino que podrías devolver el currentProgress junto con el destination y armar un mensaje de alguna utilidad...

    Soporto la moción.

    Pero le hago notar algo que TODO EL MUNDO no cae en cuenta:  Async/Await NO es multi-hilo por definición.  Asincrónico no quiere decir multi-hilo.  Multi-hilo es una forma de lograr un proceso asincrónico.  Como parece estar probando en consola, da la casualidad que Async/Await sí es multi-hilo en consola.  Pero si usted mueve su código a Windows Forms, por ejemplo, NO será multi-hilo y su progreso no se mostrará porque el único hilo de la aplicación estará congelado en la llamada a Task.WaitAll() y cualquier otra llamada que diga await y que se ejecute en el mismo hilo (aquí depende la implementación de cada cosa).

    Si quiere aprender multi-hilo, no empiece con Async/Await.


    Jose R. MCP
    Code Samples

    miércoles, 2 de mayo de 2018 0:04
    Moderador
  • hola

    >>Como podría llevar el seguimiento del progreso cuando deseo por ejemplo realizar 10 descargas concurrentes.

    pero para reportar porcentaje alguien deberia estar llevando la cuenta de cuantos se estan descargando, el progress deberia informar a un proceso por fuera de este

    poner el Console.WriteLine dentro de cada thread sin que se comunique con el resto no sabra informar el progreso

    Ademas analizando el codigo el IProgress esta retornando la cantidad de byte que se descargan de ese documento en concreto, con lo cual la UI deberia representar 10 progress informando la cantidad de byte que va descargando de cada uno

    esto lo entenderas si analizas estas dos lineas de codigo

     var currentProgress = bytesReceivedCount * 100 / totalSize;
     progressReport?.Report((int)currentProgress);

    ali esta calculano cuanto queda del total para descargar del tamano del archivo

    saludos


    Leandro Tuttini

    Blog
    MVP Profile
    Buenos Aires
    Argentina

    miércoles, 2 de mayo de 2018 4:30
  • De antemano agradezco mucho el tiempo que se tomaron en escribir una respuesta, efectivamente hize la prueba en WPF y el proceso fue bloqueado, también me di cuenta que Task.WaitAll() no es necesario en este caso ya que contrario a lo que yo pensaba las tareas comienzan a ejecutarse apenas son añadidas a la lista de tareas por lo que no es necesario utilizar WaitAll.

    Por otro lado me parece interesante lo que menciona que Async/Await no es multi-hilo, ya que en algún lugar había leído que Task es mejor en la forma en la que gestiona las tereas atraves de los procesadores lo cual me llevó a quere aprender mas sobre esta tecnología, le agradecería mucho si dispone del tiempo pudiera explicarme o referirme a algún documento donde se explique con mas detalle como funciona esta tecnología.


    jueves, 3 de mayo de 2018 1:45
  • Hola Leandro lamentablemente por el momento no puedo poner el código original por lo que intente hacer un ejemplo sencillo que demostrara lo que quiero lograr. Y pues abusando de su confianza habra alguna mejor manera de hacerlo? la verdad no estoy muy convencido de utilizar el callback para realizar el reporte.

     public class MainViewModel : INotifyPropertyChanged
        {
            private readonly ConcurrentQueue<int> _numberQueue = new ConcurrentQueue<int>();
            private readonly ExampleClass _exampleClass = new ExampleClass();
    
            public MainViewModel()
            {
                AddTask = new DelegateCommand(Enqueue, null);
                StartTask = new DelegateCommand(Start, null);
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
            public ICommand AddTask { get; set; }
    
            public ICommand StartTask { get; set; }
    
            public ObservableCollection<TaskItem> TaskItems { get; set; } = new ObservableCollection<TaskItem>();
    
            [NotifyPropertyChangedInvocator]
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            private void Enqueue(object o)
            {
                try
                {
                    EnqueueNumber(o);
                }
                catch
                {
                    // este es un ejemplo así que catch vacio.
                }
    
            }
    
            private void EnqueueNumber(object o)
            {
                int number = Convert.ToInt32(o);
                var reporter = new TaskItem() {Name = number.ToString()};
                TaskItems.Add(reporter);
                _numberQueue.Enqueue(number);
            }
    
            private void ReportProgress(int i, int number)
            {
                var item = TaskItems.FirstOrDefault(t => t.Name == number.ToString());
                if (item != null)
                    item.PercentDone = i;
            }
    
            private void Start(object o)
            {
                while (_numberQueue.Count > 0)
                {
                    int number;
                    if (_numberQueue.TryDequeue(out number))
                        _exampleClass.DoSomething(number, new Progress<int>(progress => ReportProgress(progress, number)));
                }
            }
        }
        public class ExampleClass
        {
            public async Task DoSomething(int times, IProgress<int> progressReport)
            {
                for (int i = 0; i < times; i++)
                {
                    await Task.Delay(100);
                    progressReport?.Report(i * 100 / times);
                }
            }
        }
    
        public class TaskItem : INotifyPropertyChanged
        {
            private string _name;
            private int _percentDone;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public string Name
            {
                get { return _name; }
                set
                {
                    if (value == _name) return;
                    _name = value;
                    OnPropertyChanged();
                }
            }
    
            public int PercentDone
            {
                get { return _percentDone; }
                set
                {
                    if (value == _percentDone) return;
                    _percentDone = value;
                    OnPropertyChanged();
                }
            }
            
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

    Nota: En este ejemplo TaskItems esta enlazado con un ItemsControl el cual muestra un textblock y una barra de progreso. (había una imagen pero no me dejo subirla por ser nuevo).

    jueves, 3 de mayo de 2018 2:40