{"id":1013,"date":"2012-08-30T00:48:03","date_gmt":"2012-08-29T22:48:03","guid":{"rendered":"http:\/\/blog.rastersoft.com\/?p=1013"},"modified":"2012-08-30T00:48:03","modified_gmt":"2012-08-29T22:48:03","slug":"funciones-asincronas-en-vala","status":"publish","type":"post","link":"https:\/\/blog.rastersoft.com\/?p=1013","title":{"rendered":"Funciones as\u00edncronas en Vala"},"content":{"rendered":"<p><strong>Actualizado.<\/strong> Acabo de modificar un poco el c\u00f3digo de <a href=\"http:\/\/www.rastersoft.com\/programas\/cronopete_es.html\" target=\"_blank\">Cronopete<\/a> para que haga uso de las funciones as\u00edncronas de <a href=\"https:\/\/live.gnome.org\/Vala\" target=\"_blank\">Vala<\/a>. El motivo es eliminar, en la medida de lo posible, el uso de threads.<\/p>\n<p>Las funciones as\u00edncronas de Vala son una ayuda muy interesante a la hora de realizar tareas que requieren mucho tiempo, a la vez que se quiere evitar que la interfaz se bloquee. La soluci\u00f3n m\u00e1s inmediata en estos casos consiste en crear un <em>thread<\/em> y realizar en \u00e9l todo el proceso, pero en ocasiones es una soluci\u00f3n muy compleja, pues se necesita intercambiar datos entre \u00e9ste y el <em>thread<\/em> principal, o sincronizar ambos. La soluci\u00f3n est\u00e1 en las funciones as\u00edncronas. Estas son funciones que pueden interrumpirse y reanudarse a voluntad en cualquier punto, lo que permite realizar una forma de <a href=\"http:\/\/en.wikipedia.org\/wiki\/Cooperative_multitasking#Cooperative_multitasking.2Ftime-sharing\">multitarea cooperativa<\/a> de manera sencilla.<\/p>\n<p>Imaginemos que definimos una funci\u00f3n o m\u00e9todo as\u00edncronos as\u00ed:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">public async int AsyncFnc(int p1, int p2, out int p3, out String p4) {\n    \/\/ c\u00f3digo de la funci\u00f3n as\u00edncrona (tambi\u00e9n puede ser un m\u00e9todo de una clase)\n    [...]\n}<\/pre>\n<\/div>\n<p>Para llamar a esta funci\u00f3n, lo haremos de esta manera:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">AsyncFnc.begin(p1, p2, callback_finalizacion);<\/pre>\n<\/div>\n<p>Vemos que s\u00f3lo incluimos los par\u00e1metros de entrada, pero no los de salida. Por otro lado, tampoco recogemos el valor devuelto. Por \u00faltimo, tenemos un misterioso <em>callback_finalizacion<\/em>. Este par\u00e1metro es opcional, y s\u00f3lo hay que a\u00f1adirlo cuando se desea ejecutar alg\u00fan c\u00f3digo cuando la funci\u00f3n as\u00edncrona finalice. Es en dicha funci\u00f3n donde recogeremos los valores de salida y cualquier excepci\u00f3n que se produjese durante la ejecuci\u00f3n de la funci\u00f3n as\u00edncrona. Dicho callback es de tipo <em>GLib.AsyncReadyCallback<\/em>, y su formato es el siguiente:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">public delegate void AsyncReadyCallback (Object? source_object, AsyncResult res)<\/pre>\n<\/div>\n<p>Sin embargo, Vala nos ofrece algunas ventajas, como son <a href=\"https:\/\/live.gnome.org\/Vala\/Tutorial#Anonymous_Methods_.2BAC8_Closures\" target=\"_blank\">las funciones an\u00f3nimas y los closures<\/a>, que nos dan como ventaja el poder acceder desde el callback a variables externas a \u00e9stas. As\u00ed pues, utilizando una funci\u00f3n an\u00f3nima podemos llamar a nuestra funci\u00f3n as\u00edncrona as\u00ed:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">AsyncFnc.begin(p1, p2, (obj,res) =&gt; {\n\n    int retval;\n    retval=AsyncFnc.end(res, out p3, out p4);\n\n    \/\/ resto del c\u00f3digo del callback\n    [...]\n});<\/pre>\n<\/div>\n<p>De esta manera, cuando la funci\u00f3n as\u00edncrona llegue al final y retorne, se ejecutar\u00e1 el c\u00f3digo de <em>callback<\/em> contenido en el <em>closure<\/em>. Vemos tambi\u00e9n que llamamos a <em>AsyncFnc.end<\/em>; esta es, precisamente, la manera que tenemos de obtener el valor devuelto por la funci\u00f3n as\u00edncrona, as\u00ed como los par\u00e1metros de salida. Tambi\u00e9n es esta llamada la que debemos rodear con un <em>try\/catch()<\/em> si queremos capturar cualquier excepci\u00f3n lanzada desde dentro de la funci\u00f3n as\u00edncrona.<\/p>\n<p>As\u00ed pues, ya sabemos llamar a una funci\u00f3n as\u00edncrona, y sabemos como recibir sus resultados. \u00bfPero como hacemos para que ceda el control y devolv\u00e9rselo? No olvidemos que NO se tratan de varios threads paralelos, sino de un \u00fanico hilo de ejecuci\u00f3n.<\/p>\n<p>La respuesta est\u00e1 en <em>yield<\/em> y <em>.callback()<\/em>. La primera es una llamada que, al realizarla dentro de una funci\u00f3n as\u00edncrona, devuelve el control a la funci\u00f3n llamante. La segunda es un m\u00e9todo de la funci\u00f3n as\u00edncrona (igual que <em>.begin<\/em> y <em>.end<\/em>; podemos comparar una funci\u00f3n as\u00edncrona con un objeto que ofrece esos tres m\u00e9todos p\u00fablicos) que lo que hace es continuar la ejecuci\u00f3n de dicha funci\u00f3n justo despu\u00e9s del \u00faltimo <em>yield<\/em> ejecutado. Un detalle interesante es que las llamadas a <em>yield<\/em> se pueden situar absolutamente en cualquier parte de una funci\u00f3n as\u00edncrona, incluso dentro de un bucle, lo que nos permite, de una manera sencilla, devolver el control en cada iteraci\u00f3n para que se vayan ejecutando otras partes del c\u00f3digo, y recuperarlo para la siguiente iteraci\u00f3n.<\/p>\n<p>Ahora que ya tenemos la imagen completa podemos ver como se ejecuta el c\u00f3digo: cuando una funci\u00f3n normal llama a una funci\u00f3n as\u00edncrona, le cede el control y \u00e9sta comienza a ejecutarse hasta alcanzar el primer <em>yield<\/em>. En ese punto la llamada retorna a la funci\u00f3n normal, y sigue ejecut\u00e1ndose el c\u00f3digo situado a continuaci\u00f3n de la llamada. Cuando, en alg\u00fan punto posterior, se llame al <em>.callback()<\/em> de la funci\u00f3n as\u00edncrona, el control pasar\u00e1 al c\u00f3digo situado justo despu\u00e9s de dicho primer <em>yield<\/em>, y seguir\u00e1 ejecut\u00e1ndose hasta que encuentre otro <em>yield<\/em>, momento en que el control de la ejecuci\u00f3n pasar\u00e1 al c\u00f3digo situado justo despu\u00e9s de la llamada a <em>.callback()<\/em>. Este juego de <em>ping-pong<\/em> continuar\u00e1 hasta que la funci\u00f3n as\u00edncrona llegue al final y retorne. En ese momento se ejecutar\u00e1 el c\u00f3digo del callback y continuar\u00e1 la ejecuci\u00f3n despu\u00e9s de la \u00faltima llamada a <em>.callback()<\/em>.<\/p>\n<h2>Un ejemplo gr\u00e1fico<\/h2>\n<p>Para entender mejor este proceso, ve\u00e1moslo en forma gr\u00e1fica. Por cuestiones de claridad lo dividiremos en dos im\u00e1genes: en la primera veremos el flujo desde que se inicia la funci\u00f3n as\u00edncrona hasta que est\u00e1 a punto de acabar, y en la segunda desde que se ejecuta el <em>return<\/em> al final.<\/p>\n<p><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async1.png\" rel=\"lightbox-0\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1060\" title=\"async1\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async1.png\" alt=\"\" width=\"804\" height=\"878\" \/><\/a><\/p>\n<p>En la imagen anterior vemos la primera parte del ciclo de vida de una funci\u00f3n as\u00edncrona. Cada funci\u00f3n est\u00e1 representada por un rect\u00e1ngulo, y dentro de ella, el flujo de ejecuci\u00f3n natural es de arriba a abajo.<\/p>\n<p>Desde el <em>Main Loop<\/em> de la aplicaci\u00f3n se llama a un <em>callback <\/em>(por ejemplo, porque el usuario ha pulsado un bot\u00f3n en la interfaz gr\u00e1fica), llamado\u00a0<em>Function<\/em> en este ejemplo, y en \u00e9ste se llama a nuestra funci\u00f3n as\u00edncrona (llamada <em>AsyncFnc<\/em> en el ejemplo), que comienza a ejecutarse. En el momento en el que se alcanza el primer <em>yield<\/em>, el control vuelve al <em>callback<\/em> que la lanz\u00f3, que sigue su ejecuci\u00f3n hasta que, en un punto, \u00e9ste llama a <em>AsyncFnc.callback()<\/em>. En ese momento el flujo de ejecuci\u00f3n vuelve a la funci\u00f3n as\u00edncrona, que contin\u00faa justo en la instrucci\u00f3n que sigue al primer <em>yield<\/em>. Un poco despu\u00e9s se llega a un segundo <em>yield<\/em>, que devuelve el control al <em>callback<\/em> original.<\/p>\n<p>Cuando el <em>callback<\/em> termina, se devuelve el control al <em>Main Loop<\/em>, como siempre. Pero la funci\u00f3n as\u00edncrona a\u00fan no ha terminado. Vemos entonces que en dos puntos determinados se llama a <em>AsyncFnc.callback()<\/em>. Estas llamadas no son autom\u00e1ticas, sino que tuvieron que ser preprogramadas (por ejemplo con un temporizador, o con una llamada a <em>Idle.add(AsyncFnc.callback)<\/em> para que se ejecuten en un momento en que no hay eventos por despachar). Cada vez que se llama a dicha funci\u00f3n, la ejecuci\u00f3n de la funci\u00f3n as\u00edncrona contin\u00faa justo despu\u00e9s del \u00faltimo <em>yield<\/em>, hasta que encuentra otro, o bien hasta que finaliza la funci\u00f3n.<\/p>\n<p><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async21.png\" rel=\"lightbox-1\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1065\" title=\"async2\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async21.png\" alt=\"\" width=\"804\" height=\"878\" \/><\/a><\/p>\n<p>En el siguiente esquema, las flechas en rojo muestran el flujo de ejecuci\u00f3n cuando la funci\u00f3n as\u00edncrona llega al final: en ese momento se ejecuta el <em>callback<\/em> registrado cuando se lanz\u00f3 la funci\u00f3n (si es que existe, claro; si no se defini\u00f3, se retorna directamente), y cuando se termina \u00e9ste, se retorna al punto de llamada.<\/p>\n<h2>Llamar a una funci\u00f3n as\u00edncrona desde otra funci\u00f3n as\u00edncrona<\/h2>\n<p>A la hora de llamar a una funci\u00f3n as\u00edncrona desde otra funci\u00f3n as\u00edncrona, la primera idea que nos puede venir a la cabeza es hacer<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">\/\/ ATENCION, ESTE CODIGO NO FUNCIONARA CORRECTAMENTE\n\nasync void AsyncFnc1() {\n    int retval;\n\n    [...]\n   \u00a0AsyncFnc2( (ob,res) =&gt; {\n        retval=AsyncFnc2.end();\n\u00a0       AsyncFnc1.callback();\n    });\n    yield;\n    [...]\n}<\/pre>\n<\/div>\n<p>Aparentemente, este c\u00f3digo lanzar\u00eda\u00a0<em>AsyncFnc2<\/em> y suspender\u00eda la ejecuci\u00f3n de AsyncFnc1, pero de manera que, al terminarse la ejecuci\u00f3n de <em>AsyncFnc2<\/em>, continuar\u00eda la ejecuci\u00f3n de <em>AsyncFnc1<\/em>.<\/p>\n<p>Por desgracia, ese c\u00f3digo es probable que no funcione correctamente, como m\u00ednimo en un caso concreto: si, por la raz\u00f3n que sea, la funci\u00f3n <em>AsyncFnc2<\/em> finaliza sin llamar a <em>yield<\/em> al menos una vez, se llamar\u00e1 antes a <em>AsyncFnc1.callback()<\/em> que al <em>yield<\/em>, por lo que el c\u00f3digo fallar\u00e1. Una soluci\u00f3n a este problema ser\u00eda utilizar <em>Idle.add(AsyncFnc1.callback)<\/em> en su lugar, pero s\u00f3lo funcionar\u00e1 si tenemos un <em>Main Loop<\/em> en nuestro programa, pues ser\u00e1 \u00e9ste quien llame al callback despu\u00e9s de que la funci\u00f3n as\u00edncrona retorne tras ejecutar el <em>yield<\/em>.<\/p>\n<p>Afortunadamente, los dise\u00f1adores de Vala eran conscientes de que es muy com\u00fan llamar a una funci\u00f3n as\u00edncrona desde otra, por lo que simplificaron notablemente este caso concreto. As\u00ed, para llamar a <em>AsyncFnc2<\/em> desde <em>AsyncFnc1<\/em>, de manera que \u00e9sta \u00faltima contin\u00fae su ejecuci\u00f3n cuando acabe la otra, s\u00f3lo hay que hacer<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">async void AsyncFnc1() {\n    int retval;\n\n    [...]\n    retval=yield AsyncFnc2();\n    [...]\n}<\/pre>\n<\/div>\n<p>Otra ventaja es que, en este caso, no necesitamos llamar a <em>AsyncFnc2.end()<\/em> para obtener los resultados, porque se hace directamente.<\/p>\n<p>El flujo de ejecuci\u00f3n ser\u00eda as\u00ed:<\/p>\n<p><a href=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async3.png\" rel=\"lightbox-2\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-1062\" title=\"async3\" src=\"https:\/\/blog.rastersoft.com\/wp-content\/uploads\/2012\/08\/async3.png\" alt=\"\" width=\"800\" height=\"504\" \/><\/a><\/p>\n<p>Vemos que desde el <em>Main Loop<\/em> llamamos a nuestra primera funci\u00f3n as\u00edncrona, <em>AsyncFnc1<\/em>, y despu\u00e9s de un trozo de c\u00f3digo llamamos, mediante <em>yield<\/em>, a nuestra segunda funci\u00f3n as\u00edncrona, <em>AsyncFnc2<\/em>. En \u00e9sta ejecutamos algo de c\u00f3digo y llegamos a un <em>yield<\/em>. Sin embargo, \u00e9ste no retorna a AsyncFnc1, sino a la llamada anterior. Esto es as\u00ed porque <em>AsyncFnc1<\/em> lo que quiere es dormir hasta que <em>AsyncFnc2<\/em> termine.<\/p>\n<p>Tambi\u00e9n vemos que para reanudar la ejecuci\u00f3n es preciso llamar a <em>AsyncFnc2.callback()<\/em>. Esto debe tenerse muy en cuenta: en este instante es incorrecto llamar a <em>AsyncFnc1.callback()<\/em>. Por eso es fundamental que cada funci\u00f3n as\u00edncrona se encargue de programar su llamada para recuperar el control, y no delegarlo a funciones externas, pues es muy f\u00e1cil cometer errores.<\/p>\n<p>Cuando <em>AsyncFnc2<\/em> llega al final, se cede el control al c\u00f3digo de <em>AsyncFnc1<\/em>, que sigue ejecut\u00e1ndose, y al terminar, se llamar\u00eda a la funci\u00f3n de\u00a0callback de finalizaci\u00f3n (si la hubiera), y seguir\u00eda la ejecuci\u00f3n justo a continuaci\u00f3n de la llamada a <em>AsyncFnc2.callback()<\/em> (que, en este caso, simplemente seguir\u00eda con la ejecuci\u00f3n del <em>Main Loop<\/em>).<\/p>\n<h2>Casos de uso de funciones as\u00edncronas<\/h2>\n<p>El primer caso de uso de las funciones as\u00edncronas es para llamar a otras funciones as\u00edncronas. Esto, que puede parecer una perogrullada, toma sentido en cuanto descubrimos las funciones as\u00edncronas de <a href=\"http:\/\/en.wikipedia.org\/wiki\/GIO_%28GNOME%29\"><em>Gio<\/em><\/a>. Un ejemplo es el de <a href=\"http:\/\/valadoc.org\/#!api=gio-2.0\/GLib.File.copy_async\" target=\"_blank\">File.copy_async<\/a>: este m\u00e9todo de la clase <a href=\"http:\/\/valadoc.org\/#!api=gio-2.0\/GLib.File\" target=\"_blank\">File<\/a> copia un fichero de manera as\u00edncrona. De no existir, tendr\u00edamos que crear un hilo y ejecutar en \u00e9l la funci\u00f3n s\u00edncrona de copia, porque de no hacerlo as\u00ed, si el fichero fuese muy grande la interfaz gr\u00e1fica de nuestro programa se quedar\u00eda congelada durante la operaci\u00f3n.<\/p>\n<p>Hay dos maneras de llamar a estas funciones as\u00edncronas: la primera es la cl\u00e1sica, incluyendo un <em>callback<\/em> (bien como puntero a funci\u00f3n, bien como funci\u00f3n an\u00f3nima o <em>closure<\/em>) que se llame cuando termine la operaci\u00f3n; la segunda, m\u00e1s c\u00f3moda en seg\u00fan que casos, es llamarla mediante <em>yield<\/em> desde una funci\u00f3n as\u00edncrona de Vala. En este segundo caso hay que saltarse el par\u00e1metro del callback.<\/p>\n<p>El segundo caso de uso es para llamar a servicios externos mediante <a href=\"http:\/\/www.freedesktop.org\/wiki\/Software\/dbus\"><em>DBus<\/em><\/a>. De esta manera podemos aprovechar el tiempo entre el env\u00edo de la petici\u00f3n y la llegada de la respuesta. Para ello, basta con a\u00f1adir <em>async<\/em> en la definici\u00f3n de la interfaz <em>DBus<\/em>. Un ejemplo de como definir la interfaz <em>DBus<\/em> para formatear un disco, mediante el demonio <a href=\"http:\/\/www.freedesktop.org\/wiki\/Software\/udisks\"><em>UDisks<\/em><\/a>:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">[DBus (name = \"org.freedesktop.UDisks.Device\")]\ninterface Device_if : GLib.Object {\n\tpublic abstract async void FilesystemCreate(string type, string[] options) throws IOError;\n}<\/pre>\n<\/div>\n<p>Por desgracia, en este caso concreto no podemos aprovechar la funcionalidad de la llamada mediante <em>yield<\/em>, debido a que existe un <em>timeout<\/em> en las llamadas a m\u00e9todos <em>DBus<\/em> de, aproximadamente, 25 segundos, y se necesita m\u00e1s tiempo para formatear algunos soportes. Se supone que se puede ajustar este <em>timeout<\/em> mediante un modificador de Vala, pero despu\u00e9s de consultar en la lista de correo y de buscar en internet, no he conseguido que funcione. Si alguna alma caritativa nos puede iluminar&#8230;<\/p>\n<p>El siguiente caso de uso es cuando se quiere realizar una tarea larga y que consuma CPU, pero no se quieren usar threads o lanzar un proceso paralelo, sino ejecutarlo en los momentos en los que el proceso actual est\u00e9 inactivo (en el caso de una aplicaci\u00f3n gr\u00e1fica, cuando no hay eventos que procesar en la cola). La soluci\u00f3n consiste en crear una funci\u00f3n as\u00edncrona que realice la tarea, insertando llamadas a <em>yield<\/em> de manera regular. Para que la funci\u00f3n se despierte cuando la cola est\u00e1 vac\u00eda, usamos <em>Idle.add()<\/em> para llamar al m\u00e9todo <em>.callback()<\/em> de nuestra funci\u00f3n as\u00edncrona, as\u00ed:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">public async void AsyncFnc() {\n\n    \/\/ Inicializaci\u00f3n de variables y dem\u00e1s\n    [...]\n\n    \/\/ Comienza la tarea\n    while(cond==true) {\n\n        \/\/ Realizamos una iteraci\u00f3n de la tarea\n        [....]\n\n       \u00a0\/\/ Avisamos al Main Loop que queremos que nos despierte\n        \/\/ cuando no haya eventos pendientes\n        Idle.add(AsyncFnc.callback);\n\n        \/\/ Y devolvemos el control al b\u00facle principal o Main Loop\n        yield;\n    }\n\n    \/\/ La tarea ha finalizado\n    \/\/ Limpiamos todo\n    [...]\n}<\/pre>\n<\/div>\n<p>Por supuesto, podemos utilizar tambi\u00e9n un temporizador en lugar de <em>Idle<\/em>, o cualquier otro sistema que decidamos.<\/p>\n<p>Un detalle interesante que se puede ver en este ejemplo es que la llamada a <em>yield<\/em> puede ir dentro de un bucle <em>while<\/em> o <em>for<\/em> sin ning\u00fan problema.<\/p>\n<p>Por \u00faltimo, tambi\u00e9n son \u00fatiles para sincronizar <em>threads<\/em>. El truco en este caso est\u00e1 en almacenar un puntero al m\u00e9todo de callback de la funci\u00f3n as\u00edncrona que queremos sincronizar, lanzar el <em>thread<\/em>, y al final de \u00e9ste, a\u00f1adir dicho <em>callback<\/em> a la cola de mensajes. El esquema es \u00e9ste:<\/p>\n<div class=\"mycode\">\n<pre class=\"mycode\">async void AsyncFunc() {\n\n    \/\/ Aqu\u00ed almacenamos el m\u00e9todo <em>.callback()<\/em> de nuestra funci\u00f3n as\u00edncrona\n    SourceFunc callback = AsyncFunc.callback;\n\n    \/\/ Creamos la funci\u00f3n para el thread. La hacemos an\u00f3nima (closure) para que tenga acceso a la\n    \/\/ variable local <em>callback<\/em>.\n    ThreadFunc&lt;void*&gt; run = () =&gt; {\n        \/\/ C\u00f3digo del thread. Aqu\u00ed hacemos las operaciones deseadas\n        [...]\n\n        \/\/ Aqu\u00ed ya hemos terminado las operaciones, as\u00ed que\n        \/\/ a\u00f1adimos el m\u00e9todo <em>.callback()<\/em> a la cola para que se llame inmediatamente\n        Idle.add((owned) callback);\n        return null;\n    };\n\n    \/\/ Lanzamos el thread\n    Thread.create&lt;void*&gt;(run, false);\n\n    \/\/ Y esperamos a que se llame a nuestra funci\u00f3n callback\n    \/\/ que ser\u00e1 cuando termine el thread\n    yield;\n}<\/pre>\n<\/div>\n<p>En la documentaci\u00f3n oficial se encuentran <a href=\"https:\/\/live.gnome.org\/Vala\/AsyncSamples\" target=\"_blank\">tres ejemplos de funciones as\u00edncronas<\/a>, muy \u00fatiles para entender a\u00fan mejor esta \u00fatil caracter\u00edstica de Vala. En concreto aparece un ejemplo del m\u00e9todo de sincronizaci\u00f3n de <em>threads<\/em>, donde se puede ver mucho mejor la sintaxis concreta.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Actualizado. Acabo de modificar un poco el c\u00f3digo de Cronopete para que haga uso de las funciones as\u00edncronas de Vala. El motivo es eliminar, en la medida de lo posible, el uso de threads. Las funciones as\u00edncronas de Vala son una ayuda muy interesante a la hora de realizar tareas que requieren mucho tiempo, a &hellip; <a href=\"https:\/\/blog.rastersoft.com\/?p=1013\" class=\"more-link\">Seguir leyendo <span class=\"screen-reader-text\">Funciones as\u00edncronas en Vala<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[5,7],"tags":[],"class_list":["post-1013","post","type-post","status-publish","format-standard","hentry","category-programacion","category-tutoriales"],"_links":{"self":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1013","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1013"}],"version-history":[{"count":0,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=\/wp\/v2\/posts\/1013\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1013"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1013"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.rastersoft.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1013"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}