Skippov blog

Pre Vas. Pre mna. Pre dalsie generacie.
Spring.NET a podpora NHibernate

Nedavno sa mi pod ruky dostala celkom nahodou (trosku starsia) prezentacia Thomasa Hauga o kniznici Spring.NET;
http://www.springframework.net/presentations/SpringNet-ThomasHaug-2008.pdf

Vacsina pouzivatelov vratane mna najviac vyuziva core functionalitu a to IoC kontajner. Na co som bol zvedavy, je podpora inych kniznic a toolov, najma NHibernate.

Potesila ma podpora DAO patternu, nasledujuci priklad priamo z prezentacie:

using Spring.Data.NHibernate.Generic.Support;
...
class NHibernatePersonDAO : HibernateDaoSupport, IPersonDAO {
  public void SavePerson(SpringBeispiel.Entities.Person person){
    //wirft eine Spring.Dao.DataAccessException
    //falls ein Fehler auftritt
    this.HibernateTemplate.Save(person);
  } 

  public IList<Person> ListPersons() {
    return HibernateTemplate.LoadAll<Person>;
  }
  public void UpdatePerson(Person person) {
    this.HibernateTemplate.Update(person);
  }
}

A priklad na servis s podporou transakcii:

using Spring.Transaction.Interceptor;
...
class PersonServiceImpl : IPersonService {
 ...
 [Transaction(Timeout=1000)]
 public void RenameLastName(Person p1, Person p2, string lastName) {
  p1.Nachname = lastName;
  p2.Nachname = lastName;
  this.personDAO.UpdatePerson(p1);
  this.personDAO.UpdatePerson(p2);
 }
 [Transaction(ReadOnly=true)]
 public IList<Person> ListPersons()
 {
  return this.personDAO.ListPersons();
 }
}

Je dobre vediet, ze spring so sebou prinasa aj takuto existujucu infrastrukturu. Vedel by som si predstavit jej vyuzitie aj pri implementacii Repository design patternu zalozenej na NH, kde by sme sa vyhli vyvoju+testovaniu infrastruktury ziskavania session a podporu transakcii. Vyskusam a dam vediet.

pre pouzivatelov springu, nezabudnite checknut instalacny .zip archiv a nainstlaovat podporu pre VS, Resharper, xsd schemy, atd.

NHibernate: presnost ulozenia DateTime typu

Ak potrebujete ulozit typ DateTime s presnostou na milisekundy (napr ukladam transakcie a tam potrebujem co najvyssiu presnost), moze byt neprijemne zistit, ze NHibernate v kombinacii s MSSQL serverom (a tipujem, ze sa to tyka aj dalsich db serverov) vam milisekundovu cast jednoducho odsekne.

 

Preco sa to deje? Nativny .NET DateTime typ uklada cas - konkretne milisekundy s presnostou na 7 miest. MSSQL nativny DateTime iba 3 (DateTime2 uz na 7, vid t-sql msdn doc: http://msdn.microsoft.com/en-us/library/ms187819.aspx). NH standardne pouziva ADO.NET pre komunikaciu s MSSQL, a ten nam nic presnejsie ako mu databaza ponuka nevrati.

 

Kvoli tymto rozdielom NH odsekava milisekundovu cast a nechava tuto funkcnost na uzivatela.

Mozne riesenia:

 

1) ako prve by nas napadlo ukladat ticks ako Int64 - to zafunguje, avsak niekedy chceme mat cas ulozeny ako DateTime (napriklad pri rieseni problemov, ak mame logy (napr transakcii), jazyk SQL a databaza nam umozni komfortne a rychlo vyhladat problemove zaznamy).

2) slo by to za pouzitia Interceptorov, toto riesenie sa mi zda nevhodne, smrdi. Overridnut OnSave, OnFlushDirty...

public override bool OnFlushDirty(object entity, 
                                  object id, 
                              object[] currentState,
                              object[] previousState, 
                              string[] propertyNames,
                              IType[] types) 
{
    if(entity is DomainObjectICareAbout)
        for ( int i=0; i < propertyNames.Length; i++ ) {
            if ( currentState[i] is DateTime ) {
                DateTime dt = (DateTime)currentState[i];
                dt = dt.AddMilliseconds(-1 * dt.Millisecond);
                return true;
            }
        }
    }
    return false;
}


3) session.Refresh(entity) by malo zafungovat akurat je to extra query.

4) vlastny typ - implementovat IUserType, co sa mi zda najvhodnejsie riesenie.

Pekny post som nasiel tu: http://www.mostlyclean.com/post/2007/11/Increasing-DateTime-storage-precision-in-Nhibernate-(and-Castle-ActiveRecord).aspx

Z clanku ukazka:

public class PreciseDateTimeUserType : IUserType
    {
        #region IUserType Members

        ......

        public object NullSafeGet(IDataReader rs, string[] names, object owner)
        {
            int ordinal = rs.GetOrdinal(names[0]);
            if (rs.IsDBNull(ordinal))
            {
                return new NullPreciseDateTime();
            }
            else
            {
                long value = rs.GetInt64(ordinal);
                return new PreciseDateTime(value);
            }
        }

        public void NullSafeSet(IDbCommand cmd, object value, int index)
        {
            long val = ((PreciseDateTime) value);
            ((IDbDataParameter) cmd.Parameters[index]).Value = val;
        }

        .....

        #endregion
    }

Je tam priklad tak pre NH ako aj CastleRecord.

Logovanie: Enterprise Library versus POCO&Dependency Inversion[Spring]

Enterprise library je kolekcia znovu pouzitelnych a kombinovatelnych kniznic od Microsoftu roznej funkcionality (data access, logging, exception handling, caching, crypto, validation, security, atd) s cielom pouzia (nie len) vo vacsich projektoch. Kazda kniznica riesiaca isty problem sa nazyva blok a tieto bloky mozete nakombinovat az postavite pekny (lego) domcek. Posledna verzia bola vydana v oktobri 2008 a nesie verziu 4.1 (aj ked z vacsej casti obsahuje bugfixy a vylepsenie vykonnosti, preto ak pouzivate starsiu verziu, rozhodne doporucujem upgrade).
http://msdn.microsoft.com/en-us/library/cc467894.aspx

Spring.NET je trosku mensi moloch, obsahujuci okrem ineho aj IoC kontajner (alebo Dependency Inversion, ako sa Vam paci). Je vyvyjany ako open source a mometalna stabilna verzia je 1.2.0 aj ked 1.3.0 je vo verzii RC a coskoro sa dockame final release.
http://www.springframework.net/

Enterprise Library Logging Block
EL obsahuje Logging block zodpovedny za logovanie a snazi sa nam integraciu co najviac ulahcit. Architektura je zalozena na pouziti trace listenerov (podobne ako to pozname z .NET a jeho tracing funkcionality).
Zakladne a zjednodusene kroky pre integrovanie logging bloku:

  • nareferencujeme prislusne kniznice (Common a Logging z EL by mali stacit)
  • nakonfigurujeme kategorie (xml), do ktorych budeme logovat
  • nakonfigurujeme (xml), ktore trace listenery chceme pouzit (tu mame na vyber z viacerych pripravenych, vratane event log, e-mail, db, message queue, subor, WMI alebo vlastny trace listener.
  • V kode vytvorime instanciu EventLog triedy, naplnime datami, ktore by sme radi zalogovali a zavolame staticku metodu Write Enterprise library.

Priklad pouzitia
var entry = new LogEntry()
{
    // init
};
Microsoft.Practices.EnterpriseLibrary.Logging.Logger.Write(entry);

Zjednoduseny priklad konfiguracie (2 kategorie, druha loguje len do suboru, event log trace listener loguje len spravy s typom error a vyssie).

<loggingConfiguration name="Logging Application Block" tracingEnabled="true"
  defaultCategory="" logWarningsWhenNoCategoriesMatch="true">
  <listeners>
    <add
      fileName="trace.log"
      formatter="Text Formatter"
      listenerDataType="FlatFileTraceListenerData” 
      traceOutputOptions="None"
      filter="All" 
      name="FlatFile TraceListener"
    />

    <add
      source="Enterprise Library Logging"
      formatter="Text Formatter"
      log="Application"
      machineName=""
      listenerDataType="FormattedEventLogTraceListenerData"
      traceOutputOptions="None"
      filter=”Error"
      name="Formatted EventLog TraceListener"
     />
  </listeners>
  <formatters> … </formatters>
  <categorySources>
    <add switchValue=”All” name=”Errors”>
        <listeners>
            <add name=”Formatted EventLog TraceListener” />
            <add name=” FlatFile TraceListener” />
        </listeners>
    </add>
    <add switchValue=”All” name=”User Transactions”>
        <listeners>
            <add name=” FlatFile TraceListener” />
        </listeners>
    </add>
  </categorySources>
</loggingConfiguration>

Z hore uvedeneho je jasne, ze EL Logging Block nam ponuka out-of-box riesenie, lahko integrovatelne a plne konfigurovatelne. Vo vaccine pripadov budeme chciet vytvorit vlastny trace listener, pretoze zabudovane listeneri nam nebudu stacit.

Budeme potrebovat podedit od triedy CustomTraceListener, odekorovat novu triedu atributom [ConfigurationElementType(typeof(CustomTraceListenerData))] a overridnut metodu TraceData (podla toho, co potrebujeme). Jej telo bude obsahovat nasu logiku pre spracovanie instancie LogEntry (napriklad ulozenie do db, odoslanie sms, a pod), ktora do metody prichadza ako parameter.
Zatial vsetko dobre. Kde to zacina haprovat je vykon. Ak vyvyjate aplikaciu, ktora si musi frcat (typicky priklad obsluhovat xyz requestov za sekundu), Logging blok nebude dobra volba. Nedavno sme stresstestovali jeden nas live webservis, kde sme pouzili prave logging blok a boli sme zial nemilo prekvapeny vysledkami. Co to som sa docital na webe ako je architektura az priliz komplikovana, ale tie cisla boli naozaj prekvapujuce.

Ako sme testovali?
Separatna siet, dedikovane databazy a 2 webservre a load balancer (aby sme sa co najviac priblizili k live enviromentu). webservis robi pomerne komplikovane penazne transakcie a je sucastou distribuovanych transakcii cez http, takze vykon je v nasom pripade ziaduci. Servis obsahuje niekolko funkcii, kazda rozne zatazuje system, od loginu az po samotne transakcie. Cely test bezal 3 hodiny.

Orientacne vysledky
Priemerny pocet obsluzenych req/s (vsetky metody) s Logging blokom: 211
Priemerny pocet obsluzenych req/s (vsetky metody) cez Logging bloku: 272

Kde je uzke miesto?
Este doplnim, ze odhalenie uzkeho miesta nebolo az take trivialne, nakolko na aplikacii prebehol pomerne rozsiahly refactoring logovania, ktory predstavil enterprise library a z pociatku nebolo jasne, v com je problem. A tak prisiel cas pre .NET profiler a JetBrains dotTrace, vynikajuci tool, vrele doporucujem.

dotTrace odhalil, ze komunikacia v bode, ked loging bloku posleme objekt na zalogovanie do prislusnych trace listenerov (v nasom pripade to bola len databaza) trva velmi dlho. Cisla, ktore odhalil profiling:
  • 60 volani Write statickej metody Loging bloku trvalo 16.254ms (2,32% celkoveho casu)
  • 60 volani pripojeneho trace listenera (v ramci volanej metody Write), ktory data zapisal do databazy bolo dokopy 4.748ms (0,68% celkoveho casu)
Niekde sa nam straca viac ako 10.000ms, to je skutocne obrovske cislo.

Poucenie
Nakolko niektore bloky EL sa vo firme uspesne pouzivaju a samotny logging blok pouziva niekolko release teamov, boli sme malo opatrni a vyuzili predpripravene triedy a funkcnost ako priamu zavislost. Najma ked sa jednalo len o obycajne logovanie. Vymenit EL za nieco ine nebolo az take narocne, nakolko predchadzajuci refactoring vyriesil mnohe problemy a odtienil ostatnu cast kodu od implementacie logovania, ale aj tak sme si mohli usetrit cas odstranenim tejto zavislosti. Takze minimalizovat zavislosti aj od relativne nekomplikovanych casti programu. A teda interfaces a IoC.

Riesenie
Implementovali sme vlastny jednoduchy loging blok. Mozno to znie divne, ale zvazme fakty:
  • implementacia vznikla uz v ramci odhalenia vykonnostnych problemov loging bloku
  • custom trace listner sme mali implementovany
  • vykon
  • vyberat a testovat ine existujuce frameworky, ktore navyse vo velkej firme ako nasa musi schvalit ista rada architektov, dalsie polienko pod nohy
  • mame k dispozicii IoC kontajner

Ked sa tak pozrieme na hore popisany logging block z EL (a ostatny frameworky na tom budu nejako podobne), vacsinu funkcionality resp konfiguracie je napisana prave v .xml subore. Pockat, co nam este umoznuje pripravit nieco podobne? Nainicializovat a vratit objekt daneho typu. Ano spravne, IoC kontajner. V nasom pripade spring.net. V samostatnom subore nakonfigurujeme de facto to iste ako v konfiguracnom subore loging bloku.

V kode potom vytvorime 2 interfaces:
public interface ILoggingBlock
{
void Write(LogEntry data);
}

public interface ITraceListener
{
void TraceData(LogEntry data);
}


LogEntry bude nasa classa, mozeme sa inspirovat existujucimi rieseniami, napriklad takto:
public class LogEntry
{
public LogEntry()
{
this.Severity = TraceEventType.Error;
}

public object Data { get; set; }

public string Title { get; set; }

public string Message { get; set; }

public TraceEventType Severity { get; set; }

public int EventId { get; set; }

public ICollection Categories { get; set; }
}


Pricom do property Data si mozeme poslat nase custom data, ktore potom trace listner ulozi do db.

Ako bude vyzerat samotny logging blok, ktory okrem trace listeners podporuje aj kategorie? V jednoduchosti je krasa:
public class LoggingBlock : ILoggingBlock
{
public LoggingBlock(IDictionary> traceCategories)
{
this.TraceCategories = traceCategories;
}

#region Props

protected IDictionary> TraceCategories { get; set; }

public int TraceListenerFailedEventId { get; set; }

#endregion

#region ILoggingBlock Members

public void Write(LogEntry data)
{
foreach ( string category in data.Categories )
{
if ( !this.TraceCategories.ContainsKey(category) )
{
throw new Exception("Logging configuration does not contain category: " + category);
}

var traceListeners = this.TraceCategories[category];

foreach ( var tl in traceListeners )
{
try
{
tl.TraceData(data);
}
catch ( Exception ex )
{
System.Diagnostics.EventLog.WriteEntry(
"My Logging Block"
, "Warning: trace listener failed ("
+ ex.Message
+ "). Logging to other trace listeners will continue.”
+ Environment.NewLine
+ "Category: " + category
+ Environment.NewLine
+ ex.ToString()
, EventLogEntryType.Error
, TraceListenerFailedEventId
);
}
}
}
}

#endregion
}


A co trace listeners? Staci implementovat interface ITraceListener a v pripade napr windows event logu pouzit .NET building metodu System.Diagnostics.EventLog.WriteEntry(..). Cela praca na niekolko minut. Jedina komplikovanejsia cast ostava nakonfigurovanie IoC kontajnera. Ak ho v teame pouzivate, tak je to tiez brnkacka a vysledok bude prudko totozny z hore uvedenou konfiguraciou z EL. V buducnosti ak budeme chciet vymenit nas loging blok za nieco sofistikovanejsie, implementujeme interface ILoggingBlock a v konfiguracii IoC nastavime prislusne referencie a typy. To nam umozni menit loging bloky za behu, co je velmi pekne najma pocas stress testov adeptov na novu implementaciu funkcnosti. Samozrejme hore uvedena implementacia je trivialna ale je na com stavat. S pomocou IoC kontajnera nie je problem doplnit Filtre pre rozny typy LogEntry (error, warning, information, critical), event id pre jednotlive typy (aby napr nas MOM system vedel rozposlat varovne emaily, kde sa zaloguje vynimka (error) a pod). A to uz smeruje k pravdepodobne nejakemu peknemu existujucemu rieseniu, zalezi co aplikacia potrebuje. Pre nase servisy zatial viac nepotrebujeme a rychlost a jednoduchost je vyborna. Ak bude treba, vieme existujuce riesenie lahko integrovat a otestovat v porovnani s existujucim.

Na tomto mieste by som rad spomenul este jedno poucenie. Cele nastudovanie enterprise library, jej upgrade v ramci celej firmy z 4.0 na 4.1 (bug v buildin event log trace listeneri), jej integrovanie a konfigurovanie, testovanie a profilovanie zabralo pomerne dost casu oproti alternativnemu prezentovanemu rieseniu, resp obdobnej alternativy. 4.0 -> 4.1: dalsia nevyhoda uzavreteho projektu, kde cakame rok na opravenie banalnej chyby.

Vysledky reprezentuje druha hodnota zo stresstestov bez EL. Volania medzi ILoggingBlock a ITraceListener su standardne dlhe ako bezna komunikacia (s pretypovanim) v .NET. Priznam sa, ze neviem a ani sa mi to nechce zistovat, co sposobuje take zle casy v pripade EL.

Uceleny zoznam logovacich frameworkov mozeme najst napr tu:
http://www.dotnetlogging.com/
Treba mat ale vzdy napamati rozsiritelnost, vykon, konfigurovatelnost, zdrojaky (opensource), aktivita v komunite. Opravte ma ak sa mylim, ale taky popularny log4net trochu zaspal dobu. Ake su vase skusenosti s logovacimi frameworkami?

Zaver
Snazil som sa poukazat len na jedno z mnohych peknych vyuzitii IoC kontajnerov a vykonnostny problem Logging Blocku Enterprise library. Ako dalej zvysit rychlost logovania, ak je napr databaza bottleneck v pripade, ze musime toho logovat velmi vela (napr kvoli zakonom v danej krajine) za pouzitia messaging servisov (TIBCO, MSMQ) snad nabuduce.


Prakticke problemy pri konvertovani mien pomocou datoveho typu Double

Zacnime vtipom:

"Rada nad zlato. Chcete zvacsit svoj kapital? Pozerajte si vyplatu pod lupou."

Skippova poznamka: "najma po 1. januari 2009" :-)

Kazdy programator sa skor ci neskor dostane do kontaktu s aplikaciou, ktora naraba s peniazmi. Peniaze su citliva tema, najma ak sa dostane na ich reprezentaciu v pocitaci, ktory ma obmedzenu pamat (-:jedina nekonecna vec by mala byt ludska hlupost:-). Nedavno som opravoval par problemom suvisiacich s konverziou penazi do roznych mien a teraz sa mi naskytlo trosku casu to nejako zosumarizovat. Niektore veci su trivialne, ine trosku menej, nic-menej vsetko, co suvisi s peniazmi (klientov) je dolezite. Ked som problem videl prvy krat na obrazovke, hned som si spomenul na duracellkov post o problemoch datovych typov s pohyblivou desatinnou ciarkou a bol som na spravnej stope. Dufam, ze tento prispevok pomoze niekomu dalsiemu v luskani otazok suvisiacich s peniazmi.

Situacia
Ste programator znamej online 3D hry 2nd Life. Vasi klienti si mozu do systemu naliat peniaze a potom obchodovat a fungovat ako v realnom svete. Pre jednoduchost predpokladajme, ze tu mame fantasticky slovensky trh a specialne prenho sme vytvorili specialnu x-mas verziu, kde moze klient nahrat peniaze len v SKK mene (EUR este nestihlo prist).

2nd life 

Vasi klienti mozu virtualne peniaze, ktore vlozili do systemu menit za rozne veci (oblecenie, zetony do kasina, nehnutelnosti a pod.). Idu vianoce a preto budu obchodovat ako divy :-)

Problemy
Hned na uvod by som rad zdoraznil, ze dane problemy sa nesnazia byt vseobecne pre vsetky typy aplikacie, skor chcu nabadat citatela, aby sa nad nimi zamyslel v kontexte tej jeho aplikacie.

1. Zobrazenie penazi
Na prvy pohlad jasna vec, no nemusi to tak byt. Na kolko desatinnych miest zobrazime aktualny stav uctu klienta? Pri transakciach penazi tam a spat nam moze stav uctu nadobudnut velmi rozne hodnoty. Kedze pocitac nedokaze niektore cisla s nekonecnym desatinny rozvojom drzat v pamati, pracujeme vzdy s najlepsim priblizenim - aproximaciou. Kolko mieste je OK? V nasej hre by sme pokojne mohli rozhodnut, ze 2. Teda klient po urcitom case hrania a niekolkych trasakciach bude mat na svojom urcte 100,126 SKK. Zobrazime hodnotu na 2 desatinne miesta ale ako - 100,12 alebo 100,13 ?

2. Zaokruhlovanie
Zaokruhlovanie suvisi aj s bodom c.1 - zobrazenu sumu zaokruhlovat alebo orezavat? Vacsinou chceme byt ku klientom dobri a pri istych transakciach zaokruhlujeme v prospech klienta. Tu chcem zdoraznit jeden bezpecnostny problem, ak budeme na klienta prilis dobry, moze sa nam to vypomstit. Predstavme si, ze vojdeme do virtualneho kasina, kupime si za cely nas ucet zetony (nech jeden zeton == 1EUR). Ak zaokruhlujeme aj v tomto pripade v prospech klienta a nechame na strane klienta istu desatinnu cast, moze sa nam lahko stat, ze parta polsko-ruskych nadsencov nam zacne prelievat peniaze tam a spat a za jeden den nas okradne par sto az tisic korun. Obrana pred takymito utokmi vsak patri do ineho clanku.

Co ale s vypisom jeho stavu uctu. Nuz tu treba byt opatrny. Este raz, nech klient ma na svojom ucte realnych 100,126 SKK a nech padlo rozhodnutie zobrazovat stav na 2 desatinne miesta. Ako - orezat? -zaokruhlit? Nuz obe moznosti sa v nasom pripade 2nd Life daju aplikovat, s roznymi dosledkami.

Ak orezeme, klient moze mat pocit, ze mu zobrazujeme nespravne informacie (ludia nie su hlupi), nebodaj, ze sme ho okradli. To moze vazne poskodit nasu rokmi budovanu znacku a znicit cely biznis, ak je klient nespokojny a hlavne nam neveri.

Zaokruhlime? V poriadku, klient nemoze namietat, davame mu viac ako od nas ocakava (a ktomu odpovedajuci vtip: "pride chlapik na konkurz na policajta a dostane otazku: kolko je dva plus dva. Muz odpoveda: dvanast. Policajti: vyborne, berieme vas, povedali ste nam viac, ako sme od vas ocakavali"). Toto rozhodnutie nam vsak moze poriadne skomplikovat zivot, ak na inom mieste v aplikacii ponukame funkcnost typu 'zamen vsetky peniaze za zetony' a uzivatel do textoveho policka napise sumu 100,13 v domienke, ze tolko penazi naozaj ma. Po odklepnuti tlacidka vsak dostane hlasku, ze na svojom ucte nema tolko penazi, a ze si moze zamenit o nieco menej zetonov. To znamena robit dotatocnu kontrolu a rozne cesticky, ktore v konecnom dosledu a pri velkosti hry 2nd Life mozu mat negativny dopad na udrzbu, zmenu a vyvoj aplikacie. Urcite vas hned napada n-dalsich problemov.

3. Implementacia v .NET
V .NETe a C# mame 3 datove typy pracujuce podla standardu pre aritmetiku s pohyblivou desatinnou ciarkou: double, float, decimal. Ktory typ pouzit. Decimal je urceny na pracu s penaznymi hodnotami a mal by sa na to pouzivat. Je vsak asi 40x pomalsi ako double. Interny popis double a floatu najdete v napr v duracellkovom clanku alebo na wikipedii. 

Niekedy situaciu nemame vo svojich rukach a dostaneme pod ruky kod, ktory pracuje s double. Tiez zdoraznim jednu podstatnu vec, realna hodnota double a jeho textova reprezentacia .ToString() nie su rovnake. Nie som expert v implementacii aritmetiky s pohyblivou desatinnou ciarkou v .NET, no docital som sa, ze nie je uplna podla standardov. Mozno by sa niekto mohol vyjadrit v komentaroch, aky je momentalny stav v .NET 3.5, teda CLR 2.0 a BCL.

Jeden lahko prehliadnutelny dosledok tohto faktu je, ak pouzivame ASP.NET ViewState. Ak potrebujeme preniest stav-double cislo vo viewstate, jednoduche priradenie ViewState["mojeCislo"] = cislo; sposobi (predpokladam) zavolanie overrided .ToString() metody na vstupnom objekte. A v dalsom volani stranky a ziskani hodnoty z viewstate cez double.Parse nam vrati inu hodnotu, ako sme povodne vlozili. Tym padom dostavame neocakavane vysledky v dalsich vypoctoch.

Riesenim je ulozit cislo vo formate round-trip ViewState["mojeCislo"] = cislo.ToString("r"); Ako pise MSDN, takto ziskana reprezentacia cisla zaruci rovnake vysledny po opatovnom parsovani.

Zaver
Pri praci s aproximaciou realnych cisel je dolezite vediet, ako dana platforma pracuje a implementuje system pre aritmetiku cisel s pohyblivou desatinnou ciarkou. O to viac, ak robime porovnavania a cisla reprezentuju peniaze. Chyby v kode sa opravia lahko, tazko sa opat nadobudne dovera zakaznikov.

Dufam, ze vas mozno trosku odlahceny styl s prikladom z praxe a zaujal. Chcem dodat, ze som sa nesnazil o ziadnu reklamu spominanej hry. Jednoducho mi to padlo ako dobry programatorsko-managersky priklad, pre mnohych z vas mozno nie celkom typickej aplikacie, na ktorych denne pracujete.

Kviz
Pre naruziveho citatela ponukame v skutku jednoduchu otazku:  aka hodnota bude ulozena v booleovskej premennej b po vykonani nasledujuceho riadku (C#):
bool b = double.NaN == double.NaN

Tesim sa na Vase odpovede.

FUN: Life Management (Best Practices serie)

Život programátora nie je prechádzka ružovou záhradou a bez poriadnej organizácie pracovného času sa vynikajúce výsledky dosahujú pomerne ťažko. V tomto diely sa pozrieme na dôležitú časť Life Management-u, konkrétne Day Management programátora.

Typickou črtou programátorov je snaha vytvárať kód, ktorý bude znovu-použiteľný. Ako .NET programátori spomeňme napríklad riešenie založené na User Control, ktoré pri dobrom návrhu môže poslúžiť nie len autorovi. Najnovšie štúdie vedcov z Matematicko-Fyzikálnej fakulty prinášajú zaujímavé výsledky. Po niekoľko ročnom výskume vedci dokázali transformovať optimalizácie použiteľné pri tvorbe zdrojových kódov na ľudí. Dokázali odstrániť všetky úzke miesta (bottleneck) dňa programátora a vytvorili model, ktorý bude zrejme znamenať zmenu v živote mnohých z nás. Nech sa páči:

Na prvý pohľad niektoré veci nemusia byť každému viditeľné, jedná sa totiž o optimalizáciu najvyššej úrovne, slangovo nazývanou aj master coder alebo alone in the dark. Podobné chovanie sme boli schopní v prírode pozorovať na špecifických jedincoch, tzv. gejmerov, avšak ich správanie je jednoduchšie a vytvorenie všeobecného modelu pre programátorov sa doposiaľ nikomu nepodarilo.

Vedci sa momentálne snažia o customizáciu modelu, ich snahou je nahradiť prostredné časti modelu. Nové časti modelu majú zatiaľ kódové označenie vodka a drink. Všetky informácie sú označené ako confidential, vedci zatiaľ neposkytujú žiadne nové prehlásenia. Experimenty a testy podľa našich informácii dopadli veľmi dobre. Tak im držme palce.

Prajem pekný deň a teším sa nabudúce pri ďalšom pokračovaní.

Posted: Jun 29 2007, 09:26 AM skippo | s 1 comment(s) |
Zaradené do:
Indikátor AJAX volaní na pozadí

Pri browsení na internete som narazil na jeden veľmi pekne urobený generátor animovaných GIF-ov -
http://www.ajaxload.info/. Môže byť nápomocný všetkým, ktorí pracujú v AJAX-e a potrebuju používateľa aplikácie informovať o asynchrónnom volaní na pozadí, tzv. callback. Design stránky je jednoduchý a krásny zároveň.

Použitie
Stačí si zvoliť typ animácie (možeme si vybrať z viac ako 35tich animácii), farbu popredia a pozadia (ide zvoliť aj priehľadné) a po odkliknutí tlačidla "Generate It" sa animovaný GIF zobrazí v preview oblasti. K dispozícii je aj TOP10 najobľúbenejších animácii, ktoré vytvorili používatelia z celého sveta.

Ďalšie zbierky
Okrem tohto generátora existujú aj ďalšie zbierky animácii. bežte sa pozrieť ešte tu a tu. Prvá zbierka obsahuje sériu animácii od rôznych autorov, licencované su pod Public Domain, t.j. môže ich použiť kto chce, na čo chce. V prvom, aj druhom prípade je možnosť voľby pozadia (biela, čierna), pričom prvá zbierka umožňuje nastaviť ľubovoľnú farbu.

GMail AJAX notifikácia
Ak sa Vám páči notifikácia AJAX callback-ov ako majú Google vo svojom GMail a nie ste zdatní v CSS, potom sa mrknite sem. Autor na svojej stránke popisuje ako umiestniť animáciu do pravého-horného rohu obrazovky, plus nejaký malý pokec o ASP.NET AJAX UpdateProgress kontrolke. Ako to všetko rozbehať v našom dobrom priateľovi IE6 nájdete tu.

FUN: Lunch Management (Best Practices serie)

Poznáme to všetci, pár minút pred dvanástou, brucho programátora vyhráva ako nový iPod... je čas obeda. Človek je ale tvor spoločenský a nerád chodí do spoločnosti osamote, preto si v tomto článku predstavíme najnovšie postupy ako zmenežovať dobrý obed.

Chlapci z MSDN zatiaľ nemali čas apdejtnuť túto každodennú critical tému do sekcie best practices, preto je tento blog poctený uviesť ju ako prvý.

Uf, a máme to za sebou.

V článku sme sa zaoberali lunch managementom, predstavili sme si best practice s názorným príkladom a detailným výkladom. Praktické prevedenie je naplánované na dnes, 12:00 SEC. Veľa zdaru.

A teraz trošku seriózne :-) Tento obrázok ma veľmi pobavil (poslal mi ho kolega, originál autorom je Daniel Scheirich), pretože takto presne to vyzerá u nas vo firme cca o 11:30 päť dní v týždni. Teraz sa priznajte, kto ste na tom rovnako (zle) :-)

Posted: Jun 21 2007, 08:53 AM skippo | s 5 comment(s)
Zaradené do:
Singletony, HttpContext a ThreadStatic alebo čo je v skutočnosti private-local v ASP.NET

Ahoj všetci. Nakoľko je toto môj prvý post na tomto blogu, rád by som na úvod napísal pár vecí o mne a o tom, čomu sa chcem venovať. Programujem už nejaký ten rôčik, mal som tú česť pracovať na širokej škále projektov, od windows a web aplikácii, cez skriptovacie jazyky, až po komplikovanejšie paralelné aplikácie na Linuxovom klastri. S ASP.NET robím pomerne krátko, takže moje príspevky profíkom zrejme veľa nedajú, no ďalšie generácie slovenských, a možno aj českých programátorov, tu možno objavia stratégiu bojov, ktoré sme zvádzali snáď všetci. A v neposledom rade možno svojou troškou podporím slovenskú a českú .NET komunitu. Myslím, že som konečne objavil oblasť (.NET, webové aplikácie), ktorej sa chcem dlhodobo venovať. V práci sa venujem prevažne užívateľskej strane projektov, moje príspevky sa budú týkať oblastí ako Ajax, JavaScript, CSS, ASP.NET a bezpečnosť. Opakujem prevažne. :-) V rámci toho mála voľného času sa zaoberám grafikou a tvorbou oldschool hier. Ak má niekto podobné hobby, ozvite sa, rád si pokecám a vymením skúsenosti. A teraz späť k téme.

Singleton je už klasik medzi návrhovými vzormi (design patterns). Jednoducho povedané, termín singleton reprezentuje triedu, ktorá umožní vytvoriť práve a iba jednu jej inštanciu. Zvyčajne k jej metódam a vlastnostiam pristupujeme veľmi jednoducho, a to cez statickú vlastnosť (property), ktorá vráti inštanciu triedy, ak je vytvorená. V opačnom prípade ju vytvorí a vráti inštanciu. Spomínaná vlastnosť garantuje maximálne jednú inštanciu triedy. Načo je nám taký singleton dobrý? V prvom rade umožňuje tzv. lazy instantiation, čo voľne preložené znamená, že inštancia triedy sa vytvorí až v momente, ked budeme k triede pristupovať (ak vôbec). Ďalej oproti static metódam a properties je singleton trieda thread-safe, t.j. volanie metód sa nám nezgulášuje. Tu nie som celkom presný, singleton môže byť implementovaný ako not-thread-safe, takejto implementácii by sme sa mali vyhnúť.

Príklad je lepší ako sto slov, preto ukážka jednoduchého thread-safe singletonu:

public class MojSingleton
{
    private static MojSingleton m_oInstance=null;
    private static readonly object padlock = new object();

    private MojSingleton ()
    {
    }

    public static MojSingleton oInstance
    {
        get
        {
            lock (padlock)
            {
                if (m_oInstance==null)
                {
                    m_oInstance = new MojSingleton();
                }
                return m_oInstance;
            }
        }
    }
}

Pre tých, ktorí budú ohŕňať nos nad zamykaním obsahu gettera, napíšem len to, že je to rovnaký problém ako klasická snaha programátorov o optimalizáciu použitia premenných alebo volania funkcie, ktorá je v dobe dnešných kompilátorov zanedbateľná. Toto v 99% nie je úzke miesto programu (bottleneck) a osobne by som sa pozrel na volanie databáz, ajax callbackov resp. dynamickú úpravu obsahu napríklad javascriptom.

Takáto implementácia je fajn, ale čo v prípade ASP.Net, kde nám každý request obsluhuje jeden z niekoľkých threadov poolu a my nechceme zdieľať pre všetky requesty jeden singleton? Šikovný pán Roman Macháček ponúka modifikované riešenie:

public class Singleton
{
    private Singleton()  {}

    public static Singleton Instance
    {
        get 
        {
            if (HttpContext.Current.Session["Singleton"]==null)
                HttpContext.Current.Session["Singleton"]=new Singleton();
            return (Singleton)HttpContext.Current.Session["Singleton"];
        }
    }
}

Niektoré moje singletony zgrupujú iba metódy a volanie konštruktora je zanedbateľné, preto sa mi ukladanie singletonu do objektu Session velmi nepozdávala. V prípade, že by som v triede ukladal nejaké privátne dáta a potreboval ich držať pre daný request počas celej session, hore uvedená implementácia by mi úplne vyhovovala. Avšak ako každý programátor, v túžbe optimalizovať moju aplikáciu som začal hľadať riešenie, ako vytvárať singleton na úrovni requestu. T.j. po zavolaní Request_End funkcie by sa singleton pekne a čisto odpratal. Pokiaľ možno automaticky. Pri svojom pátraní som sa dostal k parametru [ThreadStatic]. Aplikácia tohto parametra na static field spôsobí, že thready si nebudú field zdieľať, ale každý thread bude mať svojú vlastnú inštanciu threadu. (Poznámka na okraj: ako by ste do slovenčiny preložili termín field?). Vyzerá to dobre, stačí ak modifikujeme prvý príklad singletonu a sme za vodou. Teraz sa priznám, že som to neskúšal a neviem povedať, aké situácie by bolo treba riešiť. Uvádzam preto ešte jeden príklad singletonu, ktorý je thread-safe, ale bez uzamykania. Túto verziu budú preferovať odporcovia príkazu lock :-) A ostatní, ktorí sa s touto implementáciou ešte nestretli, sa niečo nové dozvedia. Pokial volanie konštruktora nie je časovo náročné a nespôsobí nejaké vedľajšie účinky, vo veľa prípadoch si vystačíte nie celkom lazy singletonom:

public class MojSingleton
{
    [ThreadStatic] 
    private static readonly MojSingleton m_oInstance=new MojSingleton();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit
    private static MojSingleton()
    {
    }

    private MojSingleton()
    {
    }

    public static MojSingleton oInstance
    {
        get
        {
            return m_oInstance;
        }
    }
}

[ThreadStatic] je fajn, ale nefunguje tak, ako by ste na prvý pohľad očakávali. Dá sa použiť iba v niektorých situáciach. Vysvetlím. Keď som sa dozvedel o tomto attribúte, už od začiatku ma sprevádzala akási nedôvera. Static fieldom veľmi neverím, preto aj tie pochybnosti ohľadom už toľkokrát spomínaného atribútu. Skúmal som teda ďalej, aké sú reálne možnosti jeho použitia.

“Aj ked si myslíš, že vieš čo robíš, NIE je bezpečné ukladať čokoľvek v ThreadStatic fielde, CallContext-te alebo Thread Local Storage v prípade ASP.NET aplikácie, ak je nejaká šanca, že hodnota bude nastavená pred volaním Page_Load (napríklad v IHttpModule alebo konštruktore stránky) a pristupovať sa k nej bude počas nej alebo neskôr” Piers7.

Piers vo svojom blogu dobre rozanalyzoval otázky okolo spracovania requestu ASP.Net-tom. Problém s ThreadStatic atribútom spočíva v tom, že ASP.NET nie je iba thread-pooled ale aj thread agile (-: Opäť otázka pre náruživého čitateľa: Ako preložiť posledné slovné spojenie do rodnej materčiny? :-) Zjednodušene povedané, ASP.NET každému requestu pridelí nejaký thread z poolu. Tých treadov je tam štandardne cca 25 (?). Tu je kameň úrazu, pretože neplatí tvrdenie: request je obsluhovaný threadom od jeho začiatku do konca. To anglické thread agile znamená, že ASP.NET môže (a robí) prepnúť thready počas spracovávania requestu a dokáže obslúžiť viac requestov jedným threadom. A teda jeden request može byť spracovaný viacerými threadmi. Nemáme kontrolu nad Thread Pool a životným cyklom threadov, preto atribút [ThreadStatic] nefunguje ako privátny a lokálny v rámci requestu, ale ako privátny a lokálny medzi všetkými threadmi poolu. Ak vieme kontrolovať thready, potom aj správanie atribútu je korektné, to ale neplatí pre ASP.NET.

Pri vysokej záťaži ASP.NET a rôznej časovej náročnosti spracovávaných funkcií sa môže stať, že príliš veľa I/O threadov spracováva requesty. ASP.NET potom zastaví spracovávanie requestu, odloží ho niekam do fronty, a príslušný thread bude pracovať na ďalšom requeste. Časom je request opať na rade a priradí sa mu nejaký dostupný worker thread. (poznámka: do hĺbky spracovania requestov I/O threadmi nevidím, možno by niekto v diskusii mohol ozrejmiť a lepšie konkretizovať túto časť a spojenie I/O threadov a worker threadov).

Ale späť k téme. Pri migrovaní requestu medzi threadmi nepremigruje všetko, ako by si niekto mohol myslieť, ale iba HttpContext. Dáta uložené napríklad v CallContext sú fuč. Keď už je nám známe migrovanie threadov, pri použití [ThreadStatic] nastáva jeden základný problém: ThreadStatic fieldy sú privátne a lokálne zdieľané medzi threadmi (t.j. vždy iba jeden z threadov pristupuje k dátam). Pri migrovaní sa môže ľahko stať, že iný thread dostane dáta niekoho iného. Riešením je použitie niečoho, čo je naozaj privátne a lokálne, ale vzhľadom na request. Odpoveď už zaznela - je to HttpContext. Kontext migruje medzi threadmi aj so všetkými dátami.

Veľmi pekným a odporúčaným miestom pre ukladanie dát v rámci jedného HTTP requestu je HttpContext.Current.Items. Tento objekt implementuje interfejs IDictionary, a teda ponúka elegantný prístup k dátam vo forme kľúč – hodnota. Pekné miesto na uloženie singletonov. A poďme rovno na príklad, jemne modifikovaný singleton č.2:

public class MojSingleton
{
      private MojSingleton ()
      {
      }

      private static readonly object padlock = new object();

      private static readonly string CONTEXT_ITEMS_KEY = "__MojSingleton";

      public static MojSingleton oInstance
      {
          get
          {
              lock (padlock)
              {
                  if (HttpContext.Current.Items[CONTEXT_ITEMS_KEY] == null)
                      HttpContext.Current.Items[CONTEXT_ITEMS_KEY] = new MojSingleton ();
                  return (MojSingleton)HttpContext.Current.Items[CONTEXT_ITEMS_KEY];
              }
          }
      }
}

Výhody a nevýhody. Singleton je naozaj thread-safe, dáta prežijú počas celého requestu a po ukončení je inštancia pekne odprataná z pamäte -- automaticky. Ostatné controly môžu využívať funkcie a dáta inštancie triedy a inštancia sa vytvára až vtedy, ak je potrebná. Ak potrebujete uložiť dáta medzi requestami, použite objekt Session, vid. príklad č.2. Alebo ak volanie konštruktora je drahé a uložené miesto v Session naopak. Nevýhodou je, že nižšie vrstvy vašej aplikácie musia referencovat namespace System.Web, aby mohli pristupovať k property HttpContext.Current. V prípade väčších a zložitejších aplikácií to nemusí byť dobré a žiadúce, ak napríklad plánujete neskôr použiť spodné vrstvy aplikácie pre winforms alebo console projekty. Veľa programátorov potom pristupuje priamo k threadu a ukladá dáta na nejaké storage miesto spojené s threadom, v snahe vyhnúť sa úzkej napojenosti na webové prostredie. Tento prístup však nebude fungovat v prípade ASP.NET, veriť možno iba HttpContext-tu.

A čo s [ThreadStatic] ? Nie je to nepoužiteľný atribút, avšak v ASP.NET aplikáciach by sa mal používať opatrne. Nezabudnúť, že request može byť spracovaný rôznymi threadmi počas svojho krátkeho života. Preto ukladanie dát v takomto fielde je zlým riešením. Ak však trieda, singleton, obsahuje iba metódy a žiaden stav (dáta), bude jeho použitie bezproblémové. (-: až do chvíle, keď zabudneme na ThreadStatic a pridáme do triedy nejaké data :-)

Uvítam, ak do diskusie prispejete aj svojimi poznatkami, prípadne dodatočným vysvetlením. Ak na niektorom mieste píšem nesprávne, napíšte mi a text opravím, resp. doplním. Vďaka a do threadovania.

Update: tento (jediný) príspevok som uverejnil na svojom predchádzajúcom blogu, no nakoľko je server už dlhú dobu mimo provoz, rozhodol som sa presunúť na tento portál, kde už nejaký ten čas pôsobím (pozdravujem fórum). Dúfam, že to tu vydrží dlhšie :-)

Viac príspevkov