Página 1 de 2 12 ÚltimoÚltimo
Resultados 1 al 10 de 12

Tema: Pregunta sobre hilos e interfaces gráficas en .NET

  1. #1

    Predeterminado Pregunta sobre hilos e interfaces gráficas en .NET

    Hace más de una semana terminé un programa para un proceso de selección en una empresa, que ponían como primera criba para comprobar los conocimientos de los candidatos.
    Lo hice en lenguaje C# y con la interfaz WPF, y el programa debía realizar varias tareas pesadas que dejaban "colgada" la interfaz, entonces lo suyo es poner aunque sea un mensaje en un label de "obteniendo ubicaciones" o "insertando en base de datos" para que el usuario vea que el programa está trabajando y no se ha colgado.
    Si probamos a poner simplemente una línea que actualice el texto de un label
    Código:
    this.lblEstado.Content = OBTENIENDO_DATOS;
    seguido de la operación pesada a realizar, el orden "no se respeta" y da más prioridad a la operación pesada de forma que el label no se actualiza hasta que termina la operación, a pesar de ir la línea que actualiza el label antes.

    Buscando información sobre esto di con la clase BackgroundWorker, a la que se le pueden asignar tres métodos: DoWork, donde se realiza la operación pesada en otro hilo ajeno al de la interfaz gráfica, y ProgressChanged y RunWorkerCompleted, uno para ir mostrando el progreso y otro para cuando ha terminado la operación, pero ambos se ejecutan en el mismo hilo de la interfaz gráfica y por tanto pueden acceder y modificar los elementos de esta.

    Con esto pude hacer lo que quería, asi que... ¿donde está el problema?
    Pues que les ha convencido mi ejercicio y me han citado para una entrevista en la que me harán preguntas sobre como lo hice y se comentará el código, y si me preguntaran porque usé la clase BackgroundWorker no sabría responder otra cosa más allá de "es que sin eso no se respeta el orden de las líneas y da menos prioridad a actualizar la UI", lo cual me parece muy poco convincente ya que tiene que ver con los distintos hilos de ejecución, pero no termino de entenderlo.
    ¿Alguien que pudiera explicarme esto?

    PD: El programa está terminado desde hace más de una semana, no necesito tocar nada de código ya, solo entender porque sin usar la clase BackgroundWorker la cosa no funciona como debiera.

  2. #2

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Lo que necesitas saber no es específico de .NET, sino del propio funcionamiento de Windows.

    Si tu aplicación sólo utiliza un hilo, y ese hilo tiene un proceso que consume mucha CPU, no será capaz de procesar su cola de mensajes, y por tanto ni actualizará el UI, ni procesará eventos.

    Por motivos de diseño, diseñar una jerarquía de clases que soporte multithreading es complejo, por lo que la mayoría de lenguajes lo hacen con un sólo hilo.

    Lo primero que surgió fue DoEvents (http://msdn.microsoft.com/es-es/libr....doevents.aspx) que lo que hacía era ceder el control al bucle de mensajes, hasta que todos se hubieran procesado, así las cosas se refrescaban, y luego continuaba la operación intensiva. No es lo óptimo, pero es sencillo de usar.

    Luego se inventó BackgroundWorker, que encapsula hilos de una forma bastante sencilla, manejando automaticamente los bloqueos. No es lo más eficiente del mundo, pero sirve.

  3. #3
    Vive aquí Avatar de Vixente
    Ubicación
    Albufera - Valencia
    Edad
    38
    Mensajes
    668

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Es siempre muy recomendable separar cualquier tipo de Entrada/Salida del hilo principal, que suele ser el de la UI, para evitar esas colgadas en nuestros programas. Como bien dice Guti, todo es porque ese hilo se satura con tus operaciones y no le estás dejando seguir sus procesos internos de actualizarse, dibujarse y demás.

    Hoy en día tienes diferentes alternativas en .NET para generar "hilos" diferentes, nunca probé BackgroundWorker aunque lo viera en bastantes ejemplos, lo veo más para controlar barras de instalación o de progreso y como no era exactamente lo que quería usar en su momento no le prestaría mucha atención. Si en tu programa te ha funcionado bien, te sirve y has entendido como usarlo, adelante. Pero creo que te gustará saber como funciona este concepto (multihilo o asincronismo) y de otras alternativas, algunas quizás más prácticas.

    No he tocado mucho WPF, mis programillas casi siempre han sido en Windows Forms, pero el concepto es el mismo. En casi todos los controles de WinForms tienes unos métodos llamados .BeginInvoke o .Invoke. Con estos, puedes llamar a funciones delegadas desde otros hilos, para "volver" y modificar lo que te sea menester en el hilo principal, donde normalmente residen los contorles, básicamente funcionan como 'callbacks' entre hilos. Así se respeta y se denomina lo que es el 'thread safe', para no tener problemas de estar accediendo a una zona de memoria (propiedades de un objeto que pertenece a otro hilo distinto) que no deberías. En WPF el BeginInvoke está dentro del objeto Dispatcher. Por ejemplo si tienes un botón en WinForm sería Boton.BeginInvoke() y en WPF sería Boton.Dispatcher.BeginInvoke(), pero me parece que la firma de esos métodos es distinta entre WPF y WF. La diferencia entre BeginInvoke e Invoke es que el primero es asincrono, desde el otro hilo llama al delegado y sigue con lo suyo, mientras que Invoke es sincrono, desde el otro hilo llama al delegado, pero espera en su hilo a la vuelta o respuesta.

    Para que se entienda mejor todo esto:



    En cada actualización de GUI del thread secundario, si es WinForms probablemente se esté usando BeginInvoke desde el otro hilo para llamar a un método delegado que actualice los datos en el control del hilo principal. Por ejemplo el texto de una label, o el progreso de una barra.

    Para generar hilos paralelos hay diversas formas:

    La más a "bajo nivel" que hay en C#/VB.NET es la clase Thread, la cual es obvia y sencilla por nombre, pero te permite tener un control más preciso sobre el propio hilo. Ahora bien, es tu responsabilidad hacer los controles correspondientes para no violar las zonas de memoria de otros hilos.

    Por otro lado está Task. Esta te la recomiendo ya que aunque no es más compleja que Thread y está por encima en cuanto a nivel, es mucho más práctica.
    Se puede usar de varias maneras, puedes generar variables tipo tarea en las que pasas un delegado que hace lo que quieres. Pero la tarea no se ejecuta hasta que tú digas (parecido a Thread), lo cómodo es que puedes obtener el resultado de ese hilo paralelo, muy cómodamente. Además puedes encadenar varias tareas entre sí en lo que se conoce como continuaciones.

    Por supuesto puedes combinar ambos métodos y crear por ejemplo un hilo secundario el cual vaya lanzando Tasks en paralelo (asincronas) las cuales internamente ya generar sus hilos.

    Por último, la funcionalidad estrella de la última versión de .NET (4.5) son las palabras clave await y async. Que sirve para definir métodos asincronos (async) y esperar sus resultados (await). Hacen uso de la clase Task, por lo cual es muy cómodo de usar, pero lo mejor de todo es la manera en la que se escribe el código. Mucho más sencillo y claro. Por ejemplo puedes programar tu programa sin tener que liarte con hilos, backgroundworkers, threads ni nada. Todo en un mismo sitio y mediante await y tareas asincronas, lo programas todo en "el mismo hilo".

    Hace tiempo hice un programa para hacer pings en C#. Es muy sencillo en apariencia, ya que es una ventana en WinForms, con unos paneles para separar los servidores, iconos para mostrar estados y barras para mostrar el progreso. La primera versión no usaba ningún tipo de hilo secundario y al haber varios "servidores" el programa evidentemente se colgaba a la espera de pings. Empecé usando Thread para ir lanzando los pings por separado y conseguí que funcionara bien. Más tarde por añadirle otras funcionalidades, decidí olvidar Thread y usar Task con funciones lambda para algunas cosas, tras varios problemas de "sincronización" (ojo con las lambda xD) conseguí que funcionara bien, aunque el programa tenía un comportamiento extraño (y aún lo tiene) y es que a veces los primeros pings de cada servidor fallan misteriosamente, los paquetes salen (comprobado con un sniffer), pero nunca vuelven... En cualquier caso, en esa misma temporada también hice una versión con await y async, que no variaba mucho de la de Task, pero si que tuve que retocar el código, quitando principalmente cosas de control entre hilos (BeginInvoke una de ellas) que ya no servían para nada.

    En apariencia no hay distinción entre la versión Task y async de mi programa, pero una usa .NET 4.0 (la que solo usa Task) y la otra 4.5 (async). Me hubiera quedado con la de async, pero como en el trabajo necesitaba compatibilidad con XP (.NET 4.0) me quede con esa.

    Un pequeño ejemplo de como funciona el programa:



    Cada incremento de la barra es un ping, por defecto se hacen 5 pings. En el estado de ping infinito es una tarea que contiene un bucle, el cual va haciendo los pings. Cuando toca actualizar (en la versión de .NET 4.0) aquí ves el resultado de usar BeginInvoke desde el hilo que hace el ping. Lo usaba para incrementar la barra y cambiar el icono de estado, pero también para otras cosas como por ejemplo actualizar el tooltip o guardar los resultados para otros usos.
    Última edición por Vixente; 09/08/2013 a las 14:03
    FX8320@4.5 + RL H80i + 16 GB@2133 + R9 290 TriX OC + SSD250GB + SSD120GB + HDD3TB + HDD3TB + CX750M + C. Carbide 300R + 6 Noctuas + 4K 28" + Track IR5 + HOTAS + Steam Controller
    Phenom II 945 + 4 GB + SSD120GB | Pentium G3250 + 8GB + SD120GB | 2 x Raspberry v2 | Raspberry v1 | MSI GT62VR Dominator GTX1060 + 16GB + SSD240GB + HDD1TB

  4. #4

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    En primer lugar gracias a los dos por vuestras respuestas.
    Veamos si lo he entendido bien...
    Si no haces uso ni de BackgroundWorker, ni de Thread, ni Task, ni de await/async, por defecto el programa se ejecuta en un solo hilo. Pero el problema es que todo lo que tenga que ver con la UI (actualizarla o escuchar eventos de esta) no es prioritario así que se ejecutará cuando tenga tiempo de CPU disponible, cosa que si justo después de llamar a un método que actualice un label se ejecuta una tarea pesada la actualización de la interfaz gráfica queda en espera y se hará cuando tenga tiempo de CPU disponible para ello (cuando termine la tarea pesada).
    De ahí que haya que recurrir a ejecutar la tarea pesada en un hilo aparte del principal para que ambas cosas se puedan hacer en paralelo.
    ¿Es correcto?

    Por lo demás, es cierto que BackgroundWorker no me terminó de convencer para lo que yo quería hacer, porque deja el código muy desordenado y es difícil de leer, además que está más orientado a ir actualizando barras de progreso, cosa que en mi caso no era muy factible porque al obtener lugares de Google Places se obtienen de veinte en veinte hasta un máximo de sesenta, y además no podemos saber de antemano cuantos resultados habrá en total.

    Cuando busqué información sobre esto di con Task, pero no me funcionaba como yo quería. No sé si es que yo hacía algo mal o es que servía para otra cosa.

    Sobre los Threads, aunque siendo algo de más bajo nivel una vez asimilado seguramente se entienda mucho mejor que al hacerlo de otras formas, no quería "perder" tiempo en entender como funcionaba todo. Las comillas indican que no es que me pareciera una pérdida de tiempo, de hecho me parece un tema muy interesante, si no que me corría prisa terminar el programa en un tiempo razonable, ya que aunque en la empresa no dieron fecha tope de entrega estaba claro que a a partir de cierto día cerrarían el periodo de entrega, además que si tardas más de la cuenta das como a entender que eres muy torpe. Aparte de eso jamás había tocado la API de Google Maps, ni parseado un XML, ni al principio tenia la menor idea de como iba a mostrar un mapa de Google Maps en una aplicación de escritorio... tampoco había tocado WPF ni C#, sólo VB.NET con WinForms y ASP.NET.
    Así que es entendible que no me quisiera complicar en demasía con los Threads, bastante tenía ya con lo otro.

    Sobre await/async, si bien uso Visual Studio 2012 y llegué a ver documentación sobre ello y me pareció sencillo, el problema es que en la empresa en cuestión aún siguen con VS2010, por lo que creé el proyecto para el Framework 4.0 para evitar problemas de compatibilidad cuando lo abrieran allí, y claro, me quedaba sin esas bonitas funcionalidades de la versión 4.5.


    Lo que si tengo que mirar más a conciencia son los Invokers/Callbacks/Dispatchers. Llegué a trastear con ello pero no me terminé de enterar como funcionaba, aunque con el gráfico que ha puesto Vixente ahora lo veo más claro.


    PD: Muy interesante tu bitácora, jgutierrez.

  5. #5
    Vive aquí Avatar de Vixente
    Ubicación
    Albufera - Valencia
    Edad
    38
    Mensajes
    668

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Si no haces uso ni de BackgroundWorker, ni de Thread, ni Task, ni de await/async, por defecto el programa se ejecuta en un solo hilo. Pero el problema es que todo lo que tenga que ver con la UI (actualizarla o escuchar eventos de esta) no es prioritario así que se ejecutará cuando tenga tiempo de CPU disponible, cosa que si justo después de llamar a un método que actualice un label se ejecuta una tarea pesada la actualización de la interfaz gráfica queda en espera y se hará cuando tenga tiempo de CPU disponible para ello (cuando termine la tarea pesada).
    De ahí que haya que recurrir a ejecutar la tarea pesada en un hilo aparte del principal para que ambas cosas se puedan hacer en paralelo.
    ¿Es correcto?
    Correcto. Toda operación de I/O (E/S) que sepas que puede tardar más de unos pocos milisegundos, digamos 10 o más, deberías plantearte seriamente en hacerla asíncrona.


    Cuando busqué información sobre esto di con Task, pero no me funcionaba como yo quería. No sé si es que yo hacía algo mal o es que servía para otra cosa.
    Task sirve para lo que tú quieras, al igual que Thread. Pero Task es más cómoda porque ella se encarga internamente de casi todo lo relativo con threads por tí y es un concepto de 'tarea', de la cual tú decides que devuelve, qué parametros y si puede continuar con otra tarea. Cualquier manual de C# reciente te lo explicará con mejores palabras que yo.

    Sobre los Threads, aunque siendo algo de más bajo nivel una vez asimilado seguramente se entienda mucho mejor que al hacerlo de otras formas, no quería "perder" tiempo en entender como funcionaba todo. Las comillas indican que no es que me pareciera una pérdida de tiempo, de hecho me parece un tema muy interesante, si no que me corría prisa terminar el programa en un tiempo razonable, ya que aunque en la empresa no dieron fecha tope de entrega estaba claro que a a partir de cierto día cerrarían el periodo de entrega, además que si tardas más de la cuenta das como a entender que eres muy torpe. Aparte de eso jamás había tocado la API de Google Maps, ni parseado un XML, ni al principio tenia la menor idea de como iba a mostrar un mapa de Google Maps en una aplicación de escritorio... tampoco había tocado WPF ni C#, sólo VB.NET con WinForms y ASP.NET.
    Así que es entendible que no me quisiera complicar en demasía con los Threads, bastante tenía ya con lo otro.
    Perfectamente compresinble. Pero tampoco te hace falta liarte con Thread primero para entender lo otro, porque creo que ya entiendes bastante bien lo básico. Aún así la clase Thread no es que sea muy complicada de entender o usar, lo único que te permite controlar el hilo en sí a tú manera. Task y async hacen uso de threads, pero lo hacen de manera interna y transparente para tí. Ahora bien, a la hora de modificar propiedades en controles de formularios desde otro hilo siempre es necesario algún tipo de comprobación y BeginInvoke/Invoke lo hacen perfectamente bien. Casi todos los controles disponen de esos métodos, sin embargo por ejemplo la clase Tooltip no lo hace y yo ya tuve que pegarme con esto en su momento. Me daba fallos aleatorios el programa con temas de estar accediendo a algo de otro hilo. La solución por la que opté, sabiendo que los Tooltips de mi programa se creaban en el hilo principal, fue el hacer el invoke para actualizar el texto del tooltip desde otro control que si tenía invoke. Un cristo, vamos. Pero así evite los problemas y las petadas.


    Sobre await/async, si bien uso Visual Studio 2012 y llegué a ver documentación sobre ello y me pareció sencillo, el problema es que en la empresa en cuestión aún siguen con VS2010, por lo que creé el proyecto para el Framework 4.0 para evitar problemas de compatibilidad cuando lo abrieran allí, y claro, me quedaba sin esas bonitas funcionalidades de la versión 4.5.
    Me encontré en tu misma situación con mi programa de pings, por eso opté por Task, porque ya existía en 4.0, si bien no tenía algunas cosas nuevas de 4.5, pude hacerlo igual de bien que con await/async.

    Lo que si tengo que mirar más a conciencia son los Invokers/Callbacks/Dispatchers. Llegué a trastear con ello pero no me terminé de enterar como funcionaba, aunque con el gráfico que ha puesto Vixente ahora lo veo más claro.
    Lo primero que creo que tienes que mirarte es el tema de funciones delegadas. Es simplemente una función que llama a otra, en C sería un puntero a una función. Pero se usa en muchas situaciones en las que quieres hacer o llamar a algo de forma indirecta. Aparte de delegados mirate las funciones lambda, son como funciones anónimas, pero sin declaración, simplemente se las llama e implementa al mismo tiempo con pocas líneas.
    Última edición por Vixente; 09/08/2013 a las 18:48
    FX8320@4.5 + RL H80i + 16 GB@2133 + R9 290 TriX OC + SSD250GB + SSD120GB + HDD3TB + HDD3TB + CX750M + C. Carbide 300R + 6 Noctuas + 4K 28" + Track IR5 + HOTAS + Steam Controller
    Phenom II 945 + 4 GB + SSD120GB | Pentium G3250 + 8GB + SD120GB | 2 x Raspberry v2 | Raspberry v1 | MSI GT62VR Dominator GTX1060 + 16GB + SSD240GB + HDD1TB

  6. #6
    Vive aquí Avatar de Vixente
    Ubicación
    Albufera - Valencia
    Edad
    38
    Mensajes
    668

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Ampliando mi respuesta, aquí un pequeño ejemplo que me he parado a confeccionar, mostrando diversos ejemplos de lo que te he estado comentando, además de un vídeo mostrando el comportamiento de ese código.

    EDIT: Aquí el proyecto de VS2012 -> https://www.dropbox.com/s/f9e49hd98r...incronismo.rar

    Link al código en colorines: http://pastebin.com/jc6wgSXP

    Código:
    public partial class Form1 : Form
        {
            // El delegado que vamos a usar para invocar con BeginInvoke...
            public delegate void Invokador();
    
            public Form1()
            {
                InitializeComponent();
    
                //Cambiamos la barra para que esté animada desde que se carga el form
                barra.Style = ProgressBarStyle.Marquee;
            }
    
            // Función que se encarga de actualizar el texto tratandolo como si estuviera en el mismo hilo
            public void actualizaTexto()
            {
                Texto.Text += "TEXTO ";
            }
    
            // Función para limpiar el texto
            public void limpiaTexto()
            {
                Texto.Text = "";
            }
    
            // Función que comprueba si necesitamos invocar (Estamos en otro hilo?) y entonces invoca al delegado el cual recibe una
            // lambda ( () => ) la cual llama a la funcion que actualiza el texto (en ese punto ya estamos en el hilo principal)
            public void actualizaTextoAsincronamente()
            {
                if (Texto.InvokeRequired) Texto.BeginInvoke(new Invokador(() => actualizaTexto()));
                // Si sabes seguro que estas en otro hilo, te puedes ahorrar el InvokeRequired, pero a mi me gusta ponerlo...
            }
        
            // Botón que actualiza el texto de manera sincrónica, congelando la UI.
            private void ButSincrono_Click(object sender, EventArgs e)
            {
                limpiaTexto();
    
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(50);       // Espera 50 milisegundos en cada iteración, esto acentúa el congelamiento.
                    actualizaTexto();
                }
            }
    
            // Función que crea un hilo paralelo, llamando al delegado que llama a una lambda, la cual llama a la función para actualizar
            // el texto desde otro thread distinto.
            private void ButThread_Click(object sender, EventArgs e)
            {
                limpiaTexto();
    
                Thread hilo = new Thread(new ThreadStart( () => ejecutaBucleAsincronamente() )); // Aquí declaramos el hilo.
                hilo.Start();   // Aquí arrancamos el hilo.
            }
    
            // Adaptación asincrona del bucle que se encarga de actualizar el texto. (Se usa siempre que sepamos que estamos en otro hilo)
            private void ejecutaBucleAsincronamente()
            {
                for (int i = 0; i < 100; i++)
                {
                    Thread.Sleep(50);       // Espera 50 milisegundos en cada iteración, aquí la congelación la tiene el hilo secundario.
                    actualizaTextoAsincronamente();
                }
            }
    
            // Función que hace uso de Task.Run de .NET 4.5 (En 4.0 es Task.Factory.StartNew) para lanzar una Task directamente, sin tener que declararla
            private void ButTaskRun_Click(object sender, EventArgs e)
            {
                limpiaTexto();
    
                Task.Run( () => ejecutaBucleAsincronamente() );     // Lambdas, lambdas everywhere. :)
            }
    
            // Función que declara una tarea y luego la ejecuta.
            private void ButTask_Click(object sender, EventArgs e)
            {
                limpiaTexto();
    
                Task tarea = new Task( () => ejecutaBucleAsincronamente());
    
                tarea.Start();
    
                // Aquí se muestra como utilizar una sencilla continuación mediante el uso de una lambda que llama a una msgbox.
                // Lo que se le pasa a la lambda entre paréntesis (tareaAnterior) está dentro del contexto de la lambda solamente
                // y sirve para obtener los valores de la tarea anterior, si fuera necesario.
                // Además se muestra como a las lambda se les puede poner llaves para poner más de una instrucción.
    
                tarea.ContinueWith( (tareaAnterior) => {
                    
                    // Para mostrar el uso de valores de la tarea anterior, se obtiene el bool de si esa tarea fue o no completada.
                    MessageBox.Show(tareaAnterior.IsCompleted.ToString(),"HECHO!");
    
                });     // Al cerrar las llaves de la lambda, seguimos teniendo que cerrar los paréntesis de la llamada a ContinueWith.
            }
    
    
            // Pequeño ejemplo de como hacer un par de llamadas a async
            private void ButAsync_Click(object sender, EventArgs e)
            {
                limpiaTexto();
                Bucle();
            }
    
            // Bucle está declarada como async pero en la función anterior se la llama sincrónicamente...
            public async void Bucle()
            {
                for (int i = 0; i < 1000; i++)
                    // Aguardamos a la Task que actualiza el texto.
                    await actualizaTextoAsync();
            }
    
            // Esta función es una tarea, porque para usar await se ha de hacer contra funciones u objetos que devuelvan o sean Task.
            public async Task actualizaTextoAsync()
            {
                    await Task.Delay(50);   // Aguardamos un retardo de 50 ms.
                    actualizaTexto();       // Actualizamos el texto tranquilamente, de manera normal. ;)
            }
    
    
    
        }   // Form1
    Última edición por Vixente; 09/08/2013 a las 23:17
    FX8320@4.5 + RL H80i + 16 GB@2133 + R9 290 TriX OC + SSD250GB + SSD120GB + HDD3TB + HDD3TB + CX750M + C. Carbide 300R + 6 Noctuas + 4K 28" + Track IR5 + HOTAS + Steam Controller
    Phenom II 945 + 4 GB + SSD120GB | Pentium G3250 + 8GB + SD120GB | 2 x Raspberry v2 | Raspberry v1 | MSI GT62VR Dominator GTX1060 + 16GB + SSD240GB + HDD1TB

  7. #7

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Cita Iniciado por Demon X Ver mensaje
    PD: Muy interesante tu bitácora, jgutierrez.
    Gracias Demon X!

  8. #8

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    Después de unos días de relax me he descargado tu programa de ejemplo, Vixente, y he estado estudiando como funciona. Ahora además entiendo la respuesta con varias citas que me diste, que cuando la pusiste no me enteré de mucho.
    Básicamente ha sido mirarme la sintaxis de las lambdas, que era algo que había visto un poco de pasada en Python pero que ni sabía que existieran en .NET, y entender el concepto de los delegados, que como tu bien decías y explican en el correspondiente artículo de msdn es como un puntero a función.
    El problema de que a mi en su día no me funcionara Task es porque para empezar ni siquiera había entendido bien como funcionaba BackgroundWorker, en el que el manejo de hilos es totalmente transparente y te basta con saber que puedes tocar la UI desde ProgressChanged y RunWorkerCompleted, pero no desde DoWork que es donde se hace el trabajo intensivo. Yo intentaba tocar la UI desde Task por las buenas, y claro, así no funcionaba.

    También cuando estuve enfrescado con el programa buscando información sobre como hacer todo esto la gente enredaba mucho las cosas con dispatchers, callback, invokers... cuando en tu programa de ejemplo se ve tan sencillo como declarar una variable de tipo delegate, instanciarla pasándole como parámetro la lambda deseada, y a su vez pasarle esto al método BeginInvoke del control que queramos modificar. Y claro, es básico saber manejarse con los delegados para modificar un control desde un hilo secundario, porque si no no hacemos nada.

    Una vez comprendido se ve bastante fácil, aunque async/await parece que tiene un poco más de miga, primero porque la primera llamada a await se debe hacer desde un método declarado con la palabra clave async, y que a su vez el método llamado con await sea también async y devuelva un objeto de tipo Task, donde se pueden meter varios waits y a su vez tocar controles de la UI sin necesidad de usar delegados. ¿Quiere decir esto que el método "async Task nombreMetodo()" se ejecuta en el hilo principal?

  9. #9
    Vive aquí Avatar de Vixente
    Ubicación
    Albufera - Valencia
    Edad
    38
    Mensajes
    668

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    ve tan sencillo como declarar una variable de tipo delegate, instanciarla pasándole como parámetro la lambda deseada, y a su vez pasarle esto al método BeginInvoke del control que queramos modificar.
    Revisando el tema, en el código que puse, llamaba a una lambda desde la creación del delegado para BeginInvoke, pero eso no hace falta porque directamente le pasas el nombre de una función que quieres que el hilo principal ejecute y ya está. Lo que quiero decir es que esto:

    Código:
    if (Texto.InvokeRequired) Texto.BeginInvoke(new Invokador(() => actualizaTexto()));
    No hace falta, porque al "Invokador" (El delegado) le pasas actualizaTexto y ya está. Así (sin los paréntesis de función):

    Código:
    if (Texto.InvokeRequired) Texto.BeginInvoke(new Invokador(actualizaTexto));
    No sé porque puse una lambda ahí, no hace falta, supongo que fue un despisté. ;P

    Una vez comprendido se ve bastante fácil, aunque async/await parece que tiene un poco más de miga, primero porque la primera llamada a await se debe hacer desde un método declarado con la palabra clave async, y que a su vez el método llamado con await sea también async y devuelva un objeto de tipo Task, donde se pueden meter varios waits y a su vez tocar controles de la UI sin necesidad de usar delegados.
    También puede haber métodos async que sean void. Aunque normalmente se usan como "lanzadores" para los que son tipo Task.

    await y async internamente funcionan con hilos también, pero "antes" hacen uso de task, continuaciones y algunas cosillas más. Está explicado en muchos sitios, con muchos ejemplos. A mi personalmente me quedo bastante más claro tras ver este vídeo https://www.youtube.com/watch?v=MCW_eJA2FeY y leerme el libro Async in C Sharp 5.0 de OReilly. Está muy bien explicado en ambos sitios. En el libro está explicado como funciona con mucho detalle, hasta la médula.

    Aparte, lo que comentan en el libro también es que en caso de abuso de async/await puede darse el caso de que un programa asíncrono sea más lento que el mismo ejecutándose de forma sincrónica. Pero eso sería en casos extremos, lo que hay que tener claro es que await tiene su coste en recursos/rendimiento, pero para tareas poco o medianamente exigentes funciona de maravilla.

    ¿Quiere decir esto que el método "async Task nombreMetodo()" se ejecuta en el hilo principal?
    Respondiendo a tu pregunta: "más o menos". Cada vez que usas await básicamente se guarda un estado de ese método hasta ese punto, tanto de variables, como pila de llamadas, contexto de ejecución, etc. Para saber por donde tiene que seguir y como estaba todo. Luego ya se encarga de ejecutar la tarea en paralelo y devolver lo que tenga que devolver cuando toque.

    Para una explicación al detalle, personalmente te recomiendo el libro que te he comentado de OReilly o que busques por internet. Lo que sí que te diré es que con await y async básicamente no te tienes que preocupar de si estás o no en otro thread, directamente programas todo como si lo hicieras en modo sincrónico, en el mismo hilo. Todo el embrollo de hilos y demás se hace de manera interna y transparente para tí.

    Una imagen del libro (tema 8) que muestra un diagrama que te puede hacer entender mejor el asunto:



    Una cita de ese mismo tema:

    During the Asynchronous Operation

    Which thread actually does the asynchronous operation?

    That’s a trick question. This is asynchronous code. For typical operations like network
    requests, there are no threads at all that are blocked waiting for the operation to
    complete.

    Of course, if you’re using async to wait for a computation, for example
    using Task.Run, the thread pool thread performing the computation
    exists and is busy.

    There is a thread waiting for network requests to complete, but it is shared between all
    network requests. It’s called the IO completion portthread on Windows. When the
    network request completes, an interrupt handler in the operating system adds a job to
    a queue for the IO completion port. To perform 1000 network requests, the requests
    are all started, and as the responses arrive, they are processed in turn by the single IO
    completion port.

    In reality, there are usually a handful of IO completion port threads, to
    take advantage of multiple CPU cores. However, the number of threads
    is the same whether there are currently 10 outstanding network requests
    or 1000.
    Última edición por Vixente; 25/08/2013 a las 16:05
    FX8320@4.5 + RL H80i + 16 GB@2133 + R9 290 TriX OC + SSD250GB + SSD120GB + HDD3TB + HDD3TB + CX750M + C. Carbide 300R + 6 Noctuas + 4K 28" + Track IR5 + HOTAS + Steam Controller
    Phenom II 945 + 4 GB + SSD120GB | Pentium G3250 + 8GB + SD120GB | 2 x Raspberry v2 | Raspberry v1 | MSI GT62VR Dominator GTX1060 + 16GB + SSD240GB + HDD1TB

  10. #10

    Predeterminado Re: Pregunta sobre hilos e interfaces gráficas en .NET

    La verdad es que cuando vi ahí una lambda me pareció un poco raro, porque ya era una función que tenías definida, y la gracia de las lambda es poder definir las funciones "al vuelo" pasándolas como parámetros.

    Ayer me vi el video que me aconsejaste, y aunque entendí perfectamente la primera parte, cuando sólo usa Task para las tres tareas a las que pone un tiempo de espera y hace que se ejecuten de forma concurrente, al llegar a la parte de async/await me perdí bastante, entre otras cosas porque mi nivel de inglés no me da para entender bien cuando hablan, sólo pillo algunas cosas al vuelo.

    En cambio para leer no tengo problema, así que me he descargado el libro que me aconsejaste a ver que tal. Al parecer con async/await es que se cambia totalmente la forma de pensar los programas en tareas o hilos y se pasa a definir que funciones se deben ejecutar de forma asíncrona mientras el hilo que las lanza espera a que estas terminen para seguir ejecutando las siguientes líneas, o algo así xD

    Gracias por la ayuda, es muy interesante todo esto y supongo que muy útil a la hora de desarrollar aplicaciones web distribuidas que deben dar servicio a muchos clientes.

Página 1 de 2 12 ÚltimoÚltimo

Permisos de publicación

  • No puedes crear nuevos temas
  • No puedes responder temas
  • No puedes subir archivos adjuntos
  • No puedes editar tus mensajes
  •