L’ IAsyncResult design pattern è un pattern per l’esecuzione asincrona di operazioni che viene largamente usato all’interno del framework in ogni sua parte; consiste nella scrittura di due metodi che permettono di iniziare un’operazione in modo asincrono e di un metodo che la completa, un esempio di una classe che implementa questo pattern potrebbe essere:
public class AsyncLib
{
public int Foo(int parameter)
{
...operazione
}
public IAsyncResult BeginFoo(int parameter, AsyncCallback callback, object state)
{
...chiamata a Foo(int parameter) in thread separato e ritorno di un oggeto
che implementa IAsyncResult
}
public int EndFoo(IAsyncResult asyncResult)
{
…se l’operazione è finita torno il risultato altrimenti blocco il thread che ha chiamato
}
}
il funzionamento è abbastanza semplice: chiamo BeginOperazione che ritorna immediatamente un’istanza di una classe che implementa l’interfaccia IAsyncResult che possiamo interrogare per sapere a che punto è la nostra operazione, o possiamo passare un delegate di tipo AsyncCallBack al metodo stesso per farci chiamare una volta che l’operazione è finita. Quando l’operazione asincrona è terminata(lo sappiamo grazie all’oggetto IAsyncResult o perchè ci viene chiamata la callBack) dobbiamo chiamare il metodo EndOperazione e passare l’istanza ritornata dal metodo BeginOperazione, a quel punto abbiamo in mano il risultato e possiamo farci quello che vogliamo. Questa spiegazione non è completa, ma non è lo scopo del post vi lascio il link alla documentazione ufficiale Asynchronous Programming Design Patterns. Lo stesso pattern viene implementato automaticamente se utilizziamo un delegate, ovvero viene compilata per noi una classe con i metodi Begin/End.
Se dobbiamo far implementare questo pattern alle nostre librerie la cosa più complessa da fare è quella di creare una classe che implementi IAsyncResult nel modo corretto e sincronizzare il tutto con il lavoro asincrono. Le cose da prendere in esame non sono poi così banali, dobbiamo accodare il lavoro da “qualche parte”(dipende da quale tipo di thread vogliamo utilizzare, thread pool, un nostro thread etc…),implementare ciò che l’interfaccia ci richiede(le cui cose più complesse sono come gestire l’oggetto di sincronizzazione e alcuni stati richiesti dall’oggetto in modo perfomante e corretto) e chiamare la callback in caso venga fornita alla fine dell’operazione asincrona. Problema ancora più subdolo è il fatto che la nostra libreria potrebbe essere chiamata in contesti di threading diversi, quindi in caso di callback dobbiamo assicurarci che tutto rispetti le regole di questo contesto. L’implementazione costa un pò di righe di codice che non sono così banali a chiunque.
Il team delle Task Parallel Library ha fortunatamente pensato anche a questo e ha gentilmente fatto implementare alla classe Task l’interfaccia IAsyncResult. Questo fa si che scrivere con .NET 4.0 un custom IAsyncResult diventi un gioco da ragazzi, di seguito un esempio:
public class AsyncLib
{
public int Foo(int millisecond)
{
//simulo un carico di lavoro
Thread.Sleep(millisecond);
//simulo un risultato
return new Random().Next(int.MaxValue);
}
public
IAsyncResult BeginFoo(int i,
AsyncCallback callback,
object state)
{
Task<int> task = new Task<int>(o => Foo(i), state);
task.ContinueWith(t =>
{
if (callback != null)
callback(t);
},
SynchronizationContext.Current == null ?
TaskScheduler.Default :
TaskScheduler.FromCurrentSynchronizationContext());
task.Start();
return task;
}
public int
EndFoo(
IAsyncResult asyncResult)
{
Task<int> result = asyncResult as Task<int>;
if (result == null)
throw new ArgumentException("Bad async result.");
return result.Result;
}
}
cosa ne dite?Semplicemente la nostra Begin ritorna un task che esegue prima la chiamata a Foo in modo asincrono e poi attraverso ContinueWith esegue la callback(in caso venga passata) nel constesto di threading adeguato
(TaskScheduler.FromCurrentSynchronizationContext() o TaskScheduler.Default). Se contiamo le righe di codice sono 4 per la Begin(sono dovuto andare a capo per farci stare tutto)e 3/4 per la End. Con così poco semplice codice utilizzare questo pattern sarà alla portata di tutti, in quanto l’unica cosa non chiarissima è forse il check del contesto per scegliere il TaskScheduler adeguato, ma siamo in Beta 2 magari qualcosa di meglio lo pensano, per il resto non si vede l’ombra di XXXResetEvent, lock, volatile, ThreadPool, Thread,WaitXXX etc….sono solo 2 Task uno “attaccato” alla fine dell’altro…semplice no?
…ah dimenticavo, chiaramente funziona ovunque, Console app, Windows Form app, WPF app etc…
Se volete una demo contattatemi