Avatar billede burningice Nybegynder
17. juli 2004 - 16:50 Der 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?
Avatar billede arne_v Ekspert
17. juli 2004 - 16:57 #1
Send den med over til og gem i constructor til det objekt du starter tråd med.
Avatar billede burningice Nybegynder
17. juli 2004 - 17:00 #2
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 :(
Avatar billede arne_v Ekspert
17. juli 2004 - 17:03 #3
Hvis du ikke kan lide constructor, så lav det som en set property og
brug den inden du starter tråden. På en eller anden måde skal den
jo over.
Avatar billede burningice Nybegynder
17. juli 2004 - 17:05 #4
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. ?
Avatar billede arne_v Ekspert
17. juli 2004 - 17:09 #5
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
?
Avatar billede burningice Nybegynder
17. juli 2004 - 17:11 #6
evt... noget i den retning... og den hashtable er så static så alle tråde deler den samme?
Avatar billede arne_v Ekspert
17. juli 2004 - 17:14 #7
static evt. pakket fint ind som singleton.
Avatar billede snepnet Nybegynder
17. juli 2004 - 17:19 #8
Kan du ikke bruge Thread.CurrentContext til noget ?
Avatar billede arne_v Ekspert
17. juli 2004 - 17:22 #9
Måske.

Jeg har aldrig brugt den.

Docs er heller ikke ligefrem oplysende:

This type supports the .NET Framework infrastructure and is not intended to be used directly from your code.
Avatar billede snepnet Nybegynder
17. juli 2004 - 17:28 #10
Prøvede lige selv... Det ser ikke ud til at kunne bruges til noget.
Avatar billede Slettet bruger
17. juli 2004 - 20:55 #11
Har du prøvet at smide en reference til Context med i stedet ved at bruge

public void min_method(ref HttpContext Context)
{
  koden
}
Avatar billede burningice Nybegynder
18. juli 2004 - 13:08 #12
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)

der kunne man så lægge et datacache-lag ind

Database
  |
Datalag
  |
Business Objects
  |
Data-cache
  |
UI (aspx/ascx)
Avatar billede snepnet Nybegynder
18. juli 2004 - 16:23 #13
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.

Mvh
Avatar billede arne_v Ekspert
31. juli 2004 - 23:38 #14
Lukke tid ?

Og et svar fra mig såfrem ideen med en singleton hashtable var noget værd.
Avatar billede snepnet Nybegynder
31. juli 2004 - 23:42 #15
Hov.... denne her havde jeg da helt glemt - den var ellers spændende.
Hvad endte det med cyberfessor ?
Avatar billede snepnet Nybegynder
08. august 2004 - 17:42 #16
Nå - cyberfessor er åbenbart forsvundet. Jeg lægger også et svar hvis noget af det jeg skrev skulle have undgået lodret arkivering :o)
Avatar billede arne_v Ekspert
08. august 2004 - 18:27 #17
Han vender tilbage på et tidspunkt !
Avatar billede snepnet Nybegynder
08. august 2004 - 19:19 #18
ja mon ikke :o)
Avatar billede burningice Nybegynder
16. august 2004 - 10:58 #19
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);

            try
            {
                sw.WriteLine(DateTime.Now +": Start loading products");
                sw.Flush();

                applicationState["productsAll"] = Product.GetByCategory(0);
               
                sw.WriteLine(DateTime.Now +": Finished loading products");
            }
            catch (Exception exc)
            {
                sw.WriteLine(DateTime.Now +": Error= "+ exc.ToString());
            }
            finally
            {
                sw.Close();
            }
        }
    }

brug af klassen:

            DataCacheManager dcm = new DataCacheManager(Application);
           
            Thread t = new Thread(new ThreadStart(dcm.LoadProducts));
            t.Start();
Avatar billede arne_v Ekspert
16. august 2004 - 11:10 #20
Den er meget hovsa.

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.
Avatar billede arne_v Ekspert
16. august 2004 - 12:53 #21
De finere finsser med hensyn til HttpContext etc. går langt hen over hovedet på
mig, da jeg ikke gør i ASP.NET selv.

Jeg kender ikke best practice for data caching i ASP:NET applikationer, men jeg
ville have lavet det anderledes nemlig:

Database
  |
Data-cache
  |
Datalag
  |
Business Objects
  |
UI
Avatar billede burningice Nybegynder
16. august 2004 - 14:13 #22
... 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å?
Avatar billede arne_v Ekspert
16. august 2004 - 14:43 #23
Ikke nødvendigvis hele databasen.

Og nok ikke et standard DataSet.
Avatar billede burningice Nybegynder
28. oktober 2004 - 21:10 #24
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?
Avatar billede arne_v Ekspert
28. oktober 2004 - 21:15 #25
Principielt ja.

Men der er en del ting man skal tage hensyn til.

Bl.a.:

1)  hvordan får man de invalide cachede resultater ud efter en INSERT/UPDATE/DELETE ?

2)  hvordan hiver man ud af hash tabel når den når en vis størrelse ?
Avatar billede burningice Nybegynder
28. oktober 2004 - 21:54 #26
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... :)
Avatar billede burningice Nybegynder
28. oktober 2004 - 21:56 #27
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
Avatar billede arne_v Ekspert
28. oktober 2004 - 22:02 #28
Det er da endnu være med business objects.

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 ?
Avatar billede arne_v Ekspert
28. oktober 2004 - 22:11 #29
Selvfølgelig kan det også laves.

Men det er sværere end at cache database rows.
Avatar billede burningice Nybegynder
28. oktober 2004 - 23:14 #30
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.
Avatar billede arne_v Ekspert
28. oktober 2004 - 23:24 #31
Jo men hvis en værdi fra en række i en tabel ender op i flere objekter, så bliver
det problematisk.
Avatar billede burningice Nybegynder
28. oktober 2004 - 23:36 #32
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
Avatar billede arne_v Ekspert
28. oktober 2004 - 23:44 #33
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 ?
Avatar billede burningice Nybegynder
29. oktober 2004 - 00:12 #34
tjaa... det er selvfølgelig rigtig nok... tis :(

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
Avatar billede snepnet Nybegynder
29. oktober 2004 - 01:14 #35
jamen dog... er i gået igang her igen :o)

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
Avatar billede Ny bruger Nybegynder

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.

Loading billede Opret Preview
Kategori
IT-kurser om Microsoft 365, sikkerhed, personlig vækst, udvikling, digital markedsføring, grafisk design, SAP og forretningsanalyse.

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester