Qualche settimana fa stavo sviluppando una applicazione ASP.NET che doveva generare e spedire ad una certa ora del giorno un bel mucchietto di e-mail. Considerando il sito mediamente trafficato, non ho imposto alcun timer e ho pensato di fare un controllo al session_start sull'ora attuale. Qualora il momento X fosse già passato avrei attivato un funzioncina per l'inoltro di massa.
Il dubbio mi venne quando incominciai a considerare se mi conveniva creare un thread a bassa priorità e far girare il metodo lì dentro o lanciare una più elegante chiamata asincrona. Le chiamate asincrone però, sotto sotto, vengono gestite da .NET come delle normali funzioni lanciate nella coda del thread-pool dell'applicazione, esattamente come le richieste di una pagina aspx.
Quindi mi chiesi che accadeva se all'interno di una chiamata asincrona abbassavo la priorità del thread. Ovviamente finchè la chiamata era in esecuzione il thread risulterebbe girare a bassa priorità, ma una volta che la funzione ha finito, che accade al prossimo metodo dato in pasto a quello stesso thread del pool?
Beh, scrissi questo piccolo test qui:
public delegate void TestDelegate();
public delegate void SetPriorityDelegate( ThreadPriority p );
static void Main(string[] args)
{
SetPriorityDelegate del = new SetPriorityDelegate(PriorityTest);
AsyncCallback ac = new AsyncCallback(SetPriorityCompleted);
del.BeginInvoke(ThreadPriority.Lowest, ac, del);
TestDelegate testDel = new TestDelegate(DoTest);
for(int i=0; i<100; i++)
{
ac = new AsyncCallback(DoTestCompleted);
testDel.BeginInvoke(ac, testDel);
}
Thread.Sleep(1000);
}
static void PriorityTest(ThreadPriority p)
{
Thread.CurrentThread.Priority = p;
}
static void DoTest()
{
Thread.Sleep(200);
}
static void SetPriorityCompleted(IAsyncResult ar)
{
Console.WriteLine( "Set priority -> " +
Thread.CurrentThread.Priority.ToString() );
}
static void DoTestCompleted(IAsyncResult ar)
{
Console.WriteLine( "Do test -> " +
Thread.CurrentThread.Priority.ToString() );
}
...lo feci girare e con grande stupore scoprii che il thread a priorità abbassata rimane "impestato" a vita!
Non contento ho approfondito su MSDN, trovando che effettivamente si sconsiglia l'uso del thread-pool qualora i thread debbano modificare le proprie priorità. Attenzione: si sconsiglia l'uso del thread pool, ma non delle asincrone.
Infatti ragionando un po' si può trovare un semplice scenario in cui un componente malevolo può agire rovinosamente su una applicazione ASP.NET
Supponiamo che lo sviluppatore di una ditta A, abbia creato un componente per l'inoltro di newsletter e che, nella funzione principale d'inoltro abbia deciso un abbassamento di priorità, e, inoltre, che si sia scordato o, in buona fede abbia ignorato, di riportare il thread alla priorità originaria prima di uscire. Può accadere e il tutto compilerebbe perfettamente.
Bene, ora io sviluppatore della società B, acquisto questo potentissimo componente e lo uso in una web-application. Visto che .NET mi consente di lanciare in modo asincrono qualunque metodo, decido di lanciare il SendNewsletter del fiammante componente di A in modo asincrono. Il componente A lavora spedisce le newsletter e termina, ma sorpresa delle sorprese il thread che lo ha supportato si trova a una priorità più bassa. Risultato: se ho una macchina monoprocessore il mio pool avrà più o meno 25 thread, uno dei quali, questo punto, in bassa priorità. Ho un sito bello trafficato con almeno due thread sempre sotto a generare pagine aspx. Visto che lo schedulatore dei thread lavora per code di priorità, molto probabilmente la pagina capitata al thread "infetto" non verrà mai eseguita entro il timeout.
Sunto del sunto. Uso un componente altrui di cui ignoro i dettagli implementativi, lancio una chiamata asincrona e mi ritrovo una pagina su 25 a caso in timeout sul browser. Chi la debugga una roba così?