Technika zvaná pooling je užitečná, protože dovoluje efektivně sdílet zdroje mezi uživateli. Aplikuje se typicky na vlákna nebo spojení s databází. V určitých případech však může být příčinou potíží, které nemusejí být hned zřejmé. Článek zmiňuje tři nebezpečí, která při využívání různých poolingů můžou vznikat. Popisuje, proč k nim dochází a navrhuje jejich řešení. Jejich ignorování může mít za následek vyčerpání systémových prostředků nebo bezpečností rizika.
SQL Connecting Pool
Programátor je z nejrůznějších zdrojů nabádán, aby spojení s databází navazoval na co možná nejkratší dobu. Typicky pomocí klauzule using
:
public static class Database {
public SqlConnection MyDB {
get {
return new SqlConnection("connection string");
}
}
}
using (var conn = Database.MyDB) {
...
}
Spojení s databází je uzavřeno po opuštění klauzule. ADO.NET však používá pooling pro spojení s databází. Otevřená spojení se uchovají pro pozdější použití. Nová instance SqlConnection
tedy většinou vrací již vytvořené spojení, jen zresetované (reset je obstarán jednoduchým hand-shakem).
Výchozí kapacita poolu je sto spojení. Ta v něm zůstávají poměrně dlouho. Pokud je aplikace služba pracující v mnoha vláknech, může nastat situace, že se pool zahltí databázovými spojeními, která si drží jedna aplikační doména na úkor druhé. Tomu lze zabránit vypnutím poolingu v connection stringu parametrem Pooling=False
, nebo upravit dobu, po kterou se spojení v poolu drží, parametrem ConnectionLifetime
.
Thread Pool
Vytvoření nového vlákna nepatří mezi nejrychlejší operace. Proto když nějaké vlákno skončí svou práci, je ponecháno při životě ještě nějakou chvíli. Když potom jiný kus kódu žádá o vytvoření vlákna, je mu podstrčeno vlákno neukončené a zhostí se jeho úlohy. Tím se ušetří čas a prostředky vytváření nových vláken. Tomuto systému se říká Thread Pool. Největší počet vláken, která si může držet, je takovýto:
- 1023 v .NET 4.0 32 bit
- 32768 v .NET 4.0 64 bit
- 250 na procesorové jádro v .NET 3.5
- 25 na procesorové jádro v .NET 2.0
- v .NET 1.0 Thread Pool ještě nebyl
Pokud webová stránka v ASP.NET volá webové služby, měla by být asynchronní. Pokud tomu tak není, je během vyřizování požadavku na webovou službu blokováno vlákno, které sestavuje obsah webové stránky. Pokud je HTTP požadavků na server mnoho, může se stát, že se vyčerpají všechna volná vlákna z Thread Poolu a nová už nebude možné vytvořit kvůli nedostatku paměti nebo výpočetního výkonu. Většina vláken však jen bude čekat, než odpoví webová služba. To je zbytečné. Pokud je webová stránka asynchronní, je počátek jejího zpracování obsloužen jedním vláknem, které zavolá webovou službu a ukončí se. Až webová služba odpoví, je odpověď zpracována druhým vláknem, které nakonec obslouží i konec generování stránky. Během čekání na odpověď služby není vlákno blokováno a může pracovat na jiných úlohách.
<%@ Page Language="C#" AutoEventWireup="false" CodeBehind="default.aspx.cs" Inherits="AsynchronousWebParts.Default" Async="true" %>
<!DOCTYPE html>
<html>
<head runat="server">
<title>Asynchronous Web Parts</title>
</head>
<body>
<form id="form" runat="server">
<asp:GridView ID="grid" runat="server"/>
</form>
</body>
</html>
namespace AsynchronousWebParts {
public partial class Default : System.Web.UI.Page {
SqlConnection conn;
SqlCommand cmd;
public Default() {
Load += new EventHandler(Page_OnLoad);
}
private void Page_OnLoad(object sender, EventArgs e) {
string connStr = "Data Source=|DataDirectory|database.sdf;async=true";
string sql = "SELECT * FROM [Database].[dbo].[Table]";
conn = new SqlConnection(connStr);
cmd = new SqlCommand(sql, conn);
conn.Open(); // launch data request asynchronously using page async task
Page.RegisterAsyncTask(new PageAsyncTask(
new BeginEventHandler(BeginGetData),
new EndEventHandler(FinishGetData),
new EndEventHandler(Timeout),
null, true)
);
}
IAsyncResult BeginGetData(object src, EventArgs e, AsyncCallback cb, object state) {
return cmd.BeginExecuteReader(cb, state);
}
private void FinishGetData(IAsyncResult ar) {
grid.DataSource = cmd.EndExecuteReader(ar);
grid.DataBind();
conn.Close();
}
private void Timeout(IAsyncResult ar) {
if (conn.State == ConnectionState.Open) conn.Close();
// timed out
}
}
}
Stránka musí obsahovat parametr async="true"
. Potom může registrovat asynchronní úlohu pomocí metody RegisterAsyncTask
. Použití tohoto postupu se dá zabránit zablokování procesu, který obsluhuje celý Application Pool. Myslete na to, že i když je vše uděláno synchronně a nevyskytují se žádné problémy, můžou rázem vyvstat, pokud server poskytující webovou službu spadne. Každý požadavek rázem blokuje jedno vlákno na 30 sekund (podle toho, na kolik máte nastaven timeout).
Application Pool
Každý ThreadPool je jen pro jediný proces. Aby bylo sdílení vláken a dalších prostředků dostatečně účinné i na webových serverech, jeden proces může obsluhovat několik virtuálních serverů. Tím ovšem v některých případech padají i bezpečnostní opatření. Webová aplikace na jednom virtuálním serveru může číst data serveru jiného, pokud s ním sdílí stejný Application Pool. Proto je nejlepší mít pro každého zákazníka vlastní Application Pool a jejich data tak od sebe bezpečně izolovat.