1. De Síncron a Asíncron
El que pretenc en aquest document és mostrar de manera senzilla com passar d'un mètode síncron a
un asíncron. Per fer-ho utilitzaré una petita aplicació Winforms amb un formulari que conté un botó
i una etiqueta. Simularem que quan premem el botó executa un mètode que conta el número de
registres que hi ha en un repositori de dades d'exemple, i el resultat el pinta en l'etiqueta. El que
farem és posar el codi del event del botó, primer de manera síncrona, i després de manera
asíncrona, utilitzant tècniques diferents, segons el framework de .NET.
(El projecte l'adjuntaré al final de tot del document.)
Un cop creat el projecte i el formulari, en centrem en el codi principal.
Mètode Síncron
/// <summary>
/// Versió síncrona
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
label1.Text =
RepositoryExample.CountExamples().ToString();
}
namespace ConvertSyncToAsync
{
public class RepositoryExample
{
public static int CountExamples()
{
System.Threading.
2. Thread.Sleep(5000);
return 10000;
}
}
}
En el primer requadre tenim el codi que s'executa quan premem el botó. I en el requadre de sota el
codi que simula el repositori.
Quan executem en aquest context ens trobem que mentre s'executa l'acció, el formulari queda com
bloquejat, i no podem fer res amb ell, ni moure'l. Això pot provocar desconcert en l'usuari que la fa
servir, i pot pensar que l'aplicació ha deixat de funcionar si dura molt.
Mètode Asíncron 1
En versions anteriors al .NET 4.0, per executar mètodes de manera asíncrona podíem fer servir
Threads, delegats i en winforms el BackGroundProcess, els quals no recomano, ja que s'ha
demostrat que la seva eficiència deixa molt a desitjar. En el exemple que us poso faré servir
delegats, ja que d' una manera molt senzilla tenim un resultat molt òptim.
/// <summary>
/// Versió asíncrona 1. Versions anteriors a .NET 4.0
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
Func<int> CountExamples = () => RepositoryExample.CountExamples();
CountExamples.BeginInvoke((result) =>
{
var data = (result.AsyncState as Func<int>).EndInvoke(result);
this.Invoke(new Action(() => label1.Text = data.ToString()));
}, CountExamples);
}
Si executem ara amb aquest codi veurem clarament que la interfície respon mentre executa el
procés de l´event de butó. Només cal intentar moure-la. Hem de tenir en compte però, que quan
executem en threads diferents, des d'un thread no és pot accedir a components de l'altre
directament. Els controls del formulari han estat creats per el thread del procés de la interfície,
mentre que el delegat s'executa en un altre thread. Si des d'aquet volguéssim accedir a la label
directament ens donaria un excepció. Per això cal invocar una acció des del thread principal del UI
que contingui el codi que volem executar sobre el component.
Mètode Asíncron 2
En la versió 4 del framework, es van introduir les Tasques (Task) com a classes (juntament amb altres
del namespace) que ajuden a la programació asíncrona. La veritat es que van molt bé. Solucionen
d'una manera molt planera l'execució asíncrona i la sincronització amb el context d'altres fils
d'execució, com pot ser el cotext principal des d'on s'inicien el processos de negoci, en el nostre cas
el procés del UI.
/// <summary>
/// Versió asíncrona 2. .NET 4.0. Introducció de les Tasques
3. /// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
var task = Task<int>.Factory.StartNew(() =>
RepositoryExample.CountExamples());
task.ContinueWith((result) => label1.Text =
result.Result.ToString(),
TaskScheduler.FromCurrentSynchronizationContext());
}
El resultat és el mateix en tots els casos. Però amb l'aparició de les tasques i tot el que les envolta, fa
que la programació asíncrona et sigui més fàcil d'implementar així, que no pas amb threads o
delegats. Si no poséssim el paràmetre
TaskScheduler.FromCurrentSynchronizationContext()
donaria una excepció al intentar accedir des d'un fil a fil de UI per fer l'assignació a la label. Aquest
instrucció fa que es sincronitzi el fil al el context de l'UI.
Mètode Asíncron 3
Amb la introducció del framework 4.5 han aparegut dues instruccions o declaracions que fan
referència explícita a la programació asíncrona. El async i el await. El primer va en la declaració del
mètode i indica que aquest és asíncron. El segon precedeix la execució d'un procés, i d'alguna
manera li diu que s'executi quan no molesti a ningú. És una aportació molt important al framework
(com altres) que cal estudiar molt bé, ja que millora molt els resultats. Van de la maneta de les
tasques com podem observar en el codi.
/// <summary>
/// Versió asíncrona 3. .NET 4.5. Introducció del async i await
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void button1_Click(object sender, EventArgs e)
{
var result = await RepositoryExample.CountExamplesAsync();
label1.Text = result.ToString();
}
namespace ConvertSyncToAsync
{
public class RepositoryExample
{
public static int CountExamples()
{
System.Threading.Thread.Sleep(5000);
return 10000;
}
public static Task<int> CountExamplesAsync()
{
return Task.Factory.StartNew<int>(() =>
{
4. System.Threading.Thread.Sleep(5000);
return 10000;
});
}
}
}
Si ens hi fixem l'event del botó ja el marquem com asíncron. La funció del repositori per poder-la
invocar amb un await cal que el tipus de retorn sigui un Task. I un cop dintre l'únic que fem es
executar una tasca. En el UI tractem el procés com si fos síncron, el marquem com asíncron async i
esperem les operacions internes asíncrones amb el await.
Fins aquí la petita introducció. Recomano estudiar molt el tema juntament amb l'execució en
paral·lel. No serveix de res tenir més d'un core als ordinadors, si el programes no els fan servir.
@SOACAT
http://soacat.blogspot.com