17. juli 2004 - 16:50Der er
34 kommentarer og 1 løsning
Skal have fat HttpContext fra en seperat tråd
I forbindelse med noget DataCaching starter jeg en ny tråd under Application_Start i global.asax for at loade en masse data ind i hukommelsen. I denne tråd opretter jeg nogle objecter som skal have adgang til at læse/skrive forskellige applications-variabler. Problemet er bare, at når jeg bruger HttpContext.Current får jeg bare en null-reference exception. Hvis samme metode udføres direkte fra Application_Start er der ingen problemer.
Er der nogen der umiddelbart kender en hurtig metode til at få fat i den eksisterende HttpContext fra en tråd man selv opretter i Asp.Net?
Støv, fibre og metalliske partikler kan påvirke både uptime, levetid og driftssikkerhed. Derfor arbejder flere datacentre systematisk med contamination control.
ja... det har jeg også tænkt på, men har efterhånden fået lavet mig et fint framework til alle mine Business Objects, så havde håbet på ikke at skulle pille for meget ved deres metode-navne og argumenter :(
havde mere håbet at man kunne fuske lidt med noget kommunikation mellem hovedtråden og den nyoprettede tråd under kørsel... evt med noget static variabler eller lign. ?
Du mener sådan noget a la: - normal request tråd opretter tråd - den gemmer key=tråd og value=http context i en hash table - den starter tråd - tråd fisker http context ud af hashtable med sig selv som key ?
zitchbow>> det var netop det arne_v foreslog og det jeg ville prøve at undgå for ikke at lave for meget om i mit eksisterende framework.
Dog er jeg ved at kigge på muligheden for at bygge et nyt datacache-framework oven på mine business objects som så har de metoder der skal til for at få det til at fungere med flere tråde m.m.
Det ser sådan her ud nu:
Database | Datalag | Business Objects | UI (aspx/ascx)
cyberfessor>> Nu går der jo arkitektur i den... Så jeg tillader mig lige at starte forfra :o)
Som udgangspunkt synes jeg ikke det virker som nogen god idé at dine business-objekter er afhængige/gør brug af en HttpContext... Jeg synes det kompromitterer adskildelsen, så jeg er enig med dig i at der nok skal gøres et eller andet.
Nu skrev jeg godt nok selv det med Thread.Current, men jeg tror egentlig ikke det overhovedet kan lade sig gøre, at komme fra en ny tråd og til din HttpContext (altså uden f.eks. at smide den med i konstruktøren, som foreslået et par gange). (der kommer en mulighed til længere nede, men jeg skal lige "af med noget" først :o)
Selvom du så ændrede dine konstruktører således at du kunne smide en HttpContext med over, tror jeg IKKE det vil være nogen god idé.
Hvis du starter en ny tråd, kan den jo potentielt leve i meget længere tid end den HttpContext du har smidt med over, hvilket så ville betyde at den kontekst du har gjort objektet afhængigt af, ikke kan garanteres at være tilstede... Eller kan være ændret (når et request har eksekveret færdigt er man vel prisgivet til hvad frameworket gør).
Nu skriver du jo godt nok at det er nogle applikations-variable du gerne vil have fat, hvilket muligvis nedsætter risikoen, men jeg synes alligevel at idéen med koblingen er lidt tvivlsom.
* Et bud på det oprindelige spørgsmål *
Du kan tvinge frameworket til at give dig adgang til HttpContext ved at implementere et interface på klassen (IHttpModule ser "rigtigst" ud, men også IHttpHandler kan bruges). Jeg kunne dog forestille mig at samme problematik vil være gældende her, men jeg er slet slet ikke nok inde i det til at komme med noget konkret omkring det.... Du kan jo lege lidt med det hvis det er.
Men... Du kan i hvert fald sikre dig mindst lige så "sikker" adgang til din HttpContext, fra dine tråde, uden at skulle ændre den måde du instatiere dem mv.
* Et bud til på det oprindelige spørgsmål *
Hvis du har noget i stil med nedenstånde i global.asax.cs
protected void Application_Start(Object sender, EventArgs e) { SomeClass sc = new SomeClass(); Thread t = new Thread(new ThreadStart(sc.Start)); t.Start(); }
Med SomeClass som følger :
public class SomeClass { private HttpContext hc;
public HttpContext SomeContext { get{return hc;} }
public SomeClass() { // her går det godt hc = HttpContext.Current; }
public void Start() { // her går det ikke godt ! hc = HttpContext.Current; } }
Er det værd at bemærke, at du i konstruktøre godt kan få fat i current HttpContext da objektet instantieres i "den rigtige kontekst" / fra din hovedtråd, men i metode Start() er det ikke muligt da det eksekveres i en ny tråd.
Damn... det blev en lang smøre. Jeg må jo takke dig for et inspirerende spørgsmål :o)
Håber du kunne bruge noget af det til et eller andet.
jeps, så er jeg tilbage på pinden... i må meget undskylde ventetiden, men der har været en del at se til i forbindelse med at jeg skal flytte til ålborg for at læse Datalogi.
zitchbow's svar valgte jeg ikke at bruge, da det ville bryde med mit eksisterende framework, hvilket jeg ikke var interreseret i.
arne_v's svar var lidt for kringlet og "hovsa" agtig... ved ikke om det bare er mig ;)
jeg valgte heller ikke snepnet's løsning omkring intefaces til at starte med, selvom jeg helt sikkert vil kigge mere på IHttpModule-interfacet... dog gav din anden løsning mig en idé som jeg endte med at bruge.
Helt konkret valgte jeg at lave en ny klasse, mit DataCache'lag og overføre HttpContext.Current.Application-variablen, da jeg på den måde vil være sikker på at jeg ikke pludselig stod med en null-reference fordi at HttpContext.Current var blevet nedlagt. Næst kan jeg så kalde en metode i DataCache-laget som loader mine data, og denne metode kører så i en tråd for sig selv:
public class DataCacheManager { private HttpApplicationState applicationState;
public DataCacheManager(HttpApplicationState applicationState) { this.applicationState = applicationState; }
public ProductCollection GetProductsByCategory(int category) { ProductCollection pc;
if (applicationState["productsAll"] != null) { if (category > 0) { pc = new ProductCollection(); ProductCollection cached = (ProductCollection)applicationState["productsAll"];
foreach (Product p in cached) { if (p.Category == category) { pc.Add(p); } } } else { pc = (ProductCollection)applicationState["productsAll"]; } } else { pc = Product.GetByCategory(category); }
return pc; }
public Product GetProductById(int id) { if (applicationState["productsAll"] != null) { ProductCollection cached = (ProductCollection)applicationState["productsAll"];
foreach (Product p in cached) { if (p.Id == id) { return p; } } } else { Product p = new Product(id); p.LoadData();
return p; }
throw new ArgumentException("Intet produkt med dette id"); }
public void LoadProducts() { StreamWriter sw = new StreamWriter(Configuration.BasePath +"debugLog.txt", true);
Men det er vel en af de enste løsninger som opfylder kravet som jeg forstod det nemlig at du ikke ville sende noget med over i hverken constructor eller metoder.
... men jeg vil jo cache mine enkelte BO'er, og ikke de rå data... derfor valgte jeg at lægge laget længere oppe... men du har da ret i, at man kunne cache hele db'en op i et dataset, og så arbejde ud fra det? er det det du tænker på?
hehe... nu er det godt nok ved at være et stykke tid siden.. men har da gået og tænkt lidt. Ville en ide være at lagre resultatel af en given sql-query i en hashtabel med hashværdien af queryen som key? Så ville man vel kunne se om en given query have været eksekveret før, og hvis den havde kunne man tage resultatet direkte fra hashtabellen istedet for at slå det op i databasen?
sikker på det ikke er bedre at cache Business Objecterne, istedet for de rå data? Der vil den cachede udgave jo blive opdateret i samme hug som de faktiske data'er i databasen bliver opdateret.
Eller også skal man dele sin data op i grupper, som kræver hvert sit niveau af caching. I mit faktiske eksempel opererer jeg med en stor mængde data der indbyrdes flettes på kryds og tværs i et komplekst system af trøjer, farver, størrelser, priser osv. Det er data som der ikke opdateres særlig ofte, ja, nærmest aldrig, men tager forholdsvis lang tid at hente ud da alle de forskellige Business Objects skal oprettes. Omkring 1½ minut tager det med min nuværende løsning at oprette cachen.
Man kunne måske lave nogle regler, policy's, der gjorde at det kun var bestemte tabeller der blev cached, og ændringer i dem ville medføre en... nej, så ender det med at blive en lappeløsning der kun passer til dette enkelte projekt.
Jeg har brug for noget mere generelt... arg... hehe... :)
man kan heller ikke bare oprette en hashtabel til hver tabel i databasen og tømme den når at der blev lavet ændringer i en given tabel. det vil jo gå i kage så snart man begynder at lege med JOINS
De består jo ikke nødvendigvis af data fra en tabel og en række.
Så kan det være meget svært at finde ud af hvad der er invalidt.
Forestil dig at du har diverse person og firma objekter, du redigerer et firma objekt, du opdager en fejl i et by navn for et postnummer og retter det.
Langt nede i databasen skal der rettes i postnummer-by tabellen.
Men hvilke af dine firma og person objekter er invalide i cachen ?
hvis AL redigering af data foregår gennem ens business objects, så skulle der også meget gerne være konsistens mellem databasen og ens objecter. og hvis man bliver i tvivl kan man altid vælge at flushe cachen ;) nå, ikke... gælder nok ikke i større miljøer.
ah... nu kan jeg godt se hvad du mener... ja, det er lidt af en brøler hvis man opdager det. Men det er jo næsten grund til at man må flushe sin cache, og personen der lavede sådan en fejl vil sidde tilbage med en lang næse :P
Det var så et eksempel som krævede en fejl fra start.
Men man må også kunne forestille sig mange andre eksempler på samme fænomen at samme værdi optræder i flere business objects - også eksempler uden fejl.
Sat lidt på spidsen er det så ikke forskellen på business objects og data objects at busines objects ikke har en 1:1 mapping ned i relations databasen ?
men det bliver jo et makværk at cache ens query's med mindre man vælger at flushe alt så snart der bliver ændret noget i databasen - hvilket ikke er en holdbar løsning.
Ikke så mærkeligt at jeg, nu, 2½ måned senere stadig går og får grå hår over det her
hvad med et kompromis alá nedenståede : (bare sådan lidt halvpseudomodelagtigt).
// en "info-klasse" public class SomeInfo { private static InfoDataSet _info;
private static InfoDataSet Info { if(_info == null) _info = new SomeDal().GetInfo(...); return _info; }
// metoder/properties... der kunne være en stak af dem public static string GetSomethingSpecifik(someCriteria) { // et eller andet opslag i Info }
// måske endda en public static SomeSpecializedType GetSpecialInfo(...) { // ... }
// og en mullighed for at få det suget op fra basen igen. public void Reload() { _info = null; } }
Det ville så kunne fungere som opslagsværk, men IKKE være beregnet for opdatering... Den slags kunne man bruge sit "normale" CRUD system til.
Det kunne så implementeres så det endte med en mulighed for at hente data ved : someLayer/facade/whatever .GetInfo() / .GetUncachedInfo(), som man for så vidt kunne tabbe imellem i forhold til serverbelastning, eller som en ret billig mulighed for at tilbyde en adminfunktionalitet, hvor der kan redigeres i forskellige ting og sager indtil det er iorden, og SomeInfo.Reload() eksekveres (eller indtil applikationen af andre årsager bliver recyclet :oD).
Man kan selvfølgelig få lidt bedre muligheder for styring/automatisering (og muligvis lidt performance) foræret ved at også at udnytte Cache, men det var mere princippet i det...
mvh /snep
Synes godt om
Ny brugerNybegynder
Din løsning...
Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.