[C#.NET] Event raisen op UI thread vanuit background thread

Pagina: 1
Acties:

Onderwerpen


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Hoi,

Ik heb een C# Class Library gemaakt met een klein projectje dat communiceert met een spel. Deze class library bevat een class 'SdkWrapper' waarin op een background thread een loopje elke 15 ms wat data ophaalt. Elke keer als dit gebeurt wil ik mijn SdkWrapper een event laten raisen wat mensen dan in hun eigen project kunnen opvangen.

Mijn SdkWrapper is dus zoiets:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SdkWrapper
{
    public event EventHandler<DataEventArgs> DataUpdated;

    public void Start()
    {
        Thread t = new Thread(Loop)();
        t.Start();
    }

    private void Loop()
    {
        while (running)
        {
            var data = GetData();
            var e = new DataEventArgs(data);
            this.OnDataUpdated(e);
            
            Thread.Sleep(15);
        }
    }
    
    protected virtual void OnDataUpdated(DataEventArgs e)
    {
        if (this.DataUpdated != null) this.DataUpdated(this, e);
    }
}

En mensen gebruiken hem zo:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Form1()
{
    var sdk = new SdkWrapper();
    sdk.DataUpdated += OnDataUpdated;
    sdk.Start();
}

private void OnDataUpdated(object sender, DataEventArgs e)
{
    var data = e.Data;
    
    // Gebruik data...
    txtData.Text = data.ToString();
}


Dit werkt allemaal prima.

Nu wil ik echter een detail aanpassen: ik zou graag willen dat het DataUpdated event niet in een background thread wordt geraised zoals nu het geval is. 9/10 keer zal men de data in een UI element stoppen, en dat gaat nu mis (cross-threading issues) omdat het event in een background thread zit en je daar dus niet zomaar aan de UI thread mag komen.

Nu kan men dit zelf gemakkelijk oplossen door in de event handler een method te invoken op hun eigen UI thread, maar dat wil ik voorkomen. Het idee van deze class library is om het gebruik van een andere SDK veel gemakkelijker te maken (de andere SDK is een C# wrapper voor een C++ project, maar niet gemakkelijk in gebruik), en ik vind het een beetje tegen-intuitief om mensen zelf de threading issues te laten oplossen. Als ze dat kunnen, dan kunnen ze ook wel overweg met de originele C# wrapper.

Het zou dus gemakkelijk zijn voor de gebruikers als ze meteen de UI konden gebruiken in dit event. Mijn idee was dus om het event (nou ja, de OnDataUpdated methode in SdkWrapper, die het event raised) te invoken op de UI thread.

De enige manier waarvan ik weet om iets op een UI thread te invoken is bijvoorbeeld via Control.Invoke (voor winforms) of Dispatcher.Invoke (voor WPF). Nu kan ik bijvoorbeeld een ISynchronizeInvoke laten doorgeven aan de SdkWrapper, en de OnDataUpdated methode daarop invoken. Het probleem is dan dat het nu alleen werkt voor winforms, en niet meer voor WPF omdat een WPF window niet ISynchronizeInvoke implementeert (toch?). In WPF werkt dit via een Dispatcher object voor zover ik weet, dus ik zou ook een Dispatcher kunnen laten meegeven en daarop de methode aanroepen, maar dan werkt het dus weer niet in winforms.

Ik zit dus een beetje in de knoop, het project heeft verder helemaal niets met winforms of wpf te maken dus het zou gewoon op beide moeten werken (in principe zelfs in, zeg, ASP.NET ofzo, hoewel je natuurlijk niet een spel gaat draaien op een server...), maar ik kan dus even niet inzien hoe ik dat kan realiseren omdat ik ofwel een ISynchronizeInvoke (winforms) of een Dispatcher (WPF) moet hebben.


Als enige oplossing had ik bedacht om gewoon 3 constructors te maken, eentje zonder parameters (deze raised de events gewoon in de background thread, los de cross-threading dingen dan maar zelf op), eentje die een ISynchronizeInvoke neemt en eentje die een Dispatcher neemt, en dan kijk ik gewoon welke er meegegeven is om te bepalen op welke manier ik het event raise.

Dat is ten eerste niet echt mooi lijkt me. Ten tweede moet ik daarvoor een reference naar WindowsBase hebben (dat is iets WPF specifieks geloof ik, dat is ook niet helemaal netjes als ik geen WPF gebruik, hoewel misschien niet echt een probleem). Ten slotte is het ook nog eens helemaal niet intuitief voor de gebruiker (doelgroep is mensen die misschien helemaal niet weten wat threading is, laat staan wat ze mee moeten geven als ISynchronizeInvoke of Dispatcher).


Weten jullie een nettere oplossing, liefst eentje waardoor de gebruiker zo min mogelijk 'last' van ondervind?

Bedankt!

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
Hoe zou dit precies moeten werken met meer dan 1 UI thread? Je zou iets met SynchronizationContext kunnen doen (Current opslaan, later Post/Send doen). Maar dan nog is dit een beetje een stukje magie. :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Hoezo zou er meer dan een UI thread zijn? Mensen zullen mijn code gebruiken als in mijn tweede stukje code. Dat werkt nu niet omdat de method die het DataUpdate event afhandelt op de background thread wordt aangeroepen en dus niet de txtData textbox mag gebruiken. Dat wil ik voorkomen, deze code zou gewoon moeten werken, dus zal ik er zelf voor moeten zorgen dat het event op de goeie thread wordt geraised. Door de Form mee te geven aan de SdkWrapper constructor gaat dat prima (SdkWrapper.OnDataUpdated invoken VIA ISynchronizeInvoke.Invoke), maar dan werkt het dus niet meer met WPF, tenzij men hun Window zelf ISynchronizeInvoke gaat laten implementeren...

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Hoogie2004
  • Registratie: Mei 2005
  • Laatst online: 18:51

Hoogie2004

Whohooooo

pedorus schreef op maandag 28 november 2011 @ 23:40:
Hoe zou dit precies moeten werken met meer dan 1 UI thread? Je zou iets met SynchronizationContext kunnen doen (Current opslaan, later Post/Send doen). Maar dan nog is dit een beetje een stukje magie. :p
Je hebt nog steeds maar 1 UI thread. Die van de applicatie welke de Class Library gebruikt om met de API te praten.
#edit: spuit11

@Nick, ik weet niet of je dit echt netjes kan oplossen. De netste manier is misschien nog wel simpelweg 2 (of 3) class-libraries opleveren, 1 voor WPF, 1 voor Winforms en 1 zonder iets...

Ook zou je kunnen overwegen de WPF / Winform DLL's at runtime in te laden afhankelijk van de constructor (dan heb je de harde referentie niet nodig als het goed is) om vervolgens de juiste Invoke aan te roepen. Al is dat ook niet alles. (reflectie...)

Netste manier is imho om de programmeur die jouw dll gebruikt, gewoon zelf dat te laten oplossen. Indien je de source vrijgeeft kan hij zelfs zelf in dat event die invoke neerzetten (zet er desnoods wat duidelijk commentaar bij zoals //insert invoke here).

My iRacing profile | Strava


Acties:
  • 0 Henk 'm!

  • Armageddon_2k
  • Registratie: September 2002
  • Laatst online: 10-09 15:29

Armageddon_2k

Trotse eigenaar: Yamaha R6

Kan je niet gewoon een component maken ipv een class?
Een component heeft zn eigen threads maar kan wel UI events raisen.
Je kan je class laten inheriten van System.ComponentModel.Component
Of je kan in VisualStudio een Component adden.

Het voordeel is dat je dan in je component alle thread functionaliteiten kan gooien, en je lock afhandeling ed.
Zodra je een event raised, komt deze netjes bij je UI terecht.
Ik heb op die manier een applcatie gemaakt die ongeveer hetzelfde doet als jou app.

Acties:
  • 0 Henk 'm!

  • evolution536
  • Registratie: Maart 2009
  • Laatst online: 05-06-2024

evolution536

besh besh

NickThissen schreef op maandag 28 november 2011 @ 23:24:
Hoi,

Ik heb een C# Class Library gemaakt met een klein projectje dat communiceert met een spel. Deze class library bevat een class 'SdkWrapper' waarin op een background thread een loopje elke 15 ms wat data ophaalt. Elke keer als dit gebeurt wil ik mijn SdkWrapper een event laten raisen wat mensen dan in hun eigen project kunnen opvangen.

Mijn SdkWrapper is dus zoiets:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public sealed class SdkWrapper
{
    public static event EventHandler<DataEventArgs> DataUpdated;
    private static bool running; // Ik weet niet wat dit voor variabele is, iig hij moet static zijn.

    public void Start()
    {
        Thread t = new Thread(Loop)();
        t.Start();
    }

    private static void Loop()
    {
        while (running)
        {
            var data = GetData();
            var e = new DataEventArgs(data);
            this.OnDataUpdated(e);
            
            Thread.Sleep(15);
        }
    }
    
    protected virtual void OnDataUpdated(DataEventArgs e)
    {
        if (this.DataUpdated != null) this.DataUpdated(this, e);
    }
}

En mensen gebruiken hem zo:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public Form1()
{
    var sdk = new SdkWrapper();
    sdk.DataUpdated += OnDataUpdated;
    sdk.Start();
}

private static void OnDataUpdated(object sender, DataEventArgs e)
{
    var data = e.Data;
    
    // Gebruik data...
    txtData.Text = data.ToString();
}
Dit is even uit mijn hoofd zoals ik het vroeger deed, het kan zijn dat het niet meteen goed is, maar zo heb je in principe geen problemen met cross-thread / invoken.

Zorg er als je het op deze manier doet wel altijd voor dat alle variabelen die je gebruikt in dit geval de functie Loop(), altijd static zijn. een sealed of static class is niet zo nodig, maar ja..als je dan toch een wrapper hebt met static events en functies, waarom zou je het dan je gebruikers toelaten om hun class van de jouw te deriven? :+

Als je hiervan het voorbeeld wilt zien dat ik ooit gemaakt heb moet je het even aangeven, het is een C# keylogger, gewoon in windows forms (nee niet schadelijk :+), die dit soort callbacks en static events gebruikt. Ik zou alleen wel even moeten zoeken waar ik hem had staan. Succes!

[ Voor 6% gewijzigd door evolution536 op 29-11-2011 11:33 ]


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Een static event..? Nooit gezien, eens kijken wat dat inhoudt. In ieder geval gaat je code niet werken, je roept de non-static OndataUpdated methode aan in de static Loop methode. Dan zal OnDataUpdated ook wel static moeten zijn, maar dan kan ik dus geen 'sender' meegeven. Boeit vrij weinig in dit geval, maar niet helemaal netjes denk ik?

Edit,
en ook al doe ik dat, dan krijg ik nog steeds dezelfde cross-thread melding in de event handler. Heeft dus niets veranderd.

[ Voor 16% gewijzigd door NickThissen op 29-11-2011 12:26 ]

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Armageddon_2k schreef op dinsdag 29 november 2011 @ 09:24:
Kan je niet gewoon een component maken ipv een class?
Een component heeft zn eigen threads maar kan wel UI events raisen.
Je kan je class laten inheriten van System.ComponentModel.Component
Of je kan in VisualStudio een Component adden.

Het voordeel is dat je dan in je component alle thread functionaliteiten kan gooien, en je lock afhandeling ed.
Zodra je een event raised, komt deze netjes bij je UI terecht.
Ik heb op die manier een applcatie gemaakt die ongeveer hetzelfde doet als jou app.
Een component is misschien een idee ja (maar dan zit ik weer iets teveel in winforms te werken denk ik..?). Maar hoe raise ik dan een event op de UI thread? Voor zover ik kan zien heb ik geen Invoke method of iets dergelijks (Component implementeert niet ISynchronizeInvoke)..?

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
NickThissen schreef op dinsdag 29 november 2011 @ 12:27:
(Component implementeert niet ISynchronizeInvoke)..?
Even een gokje: Ik denk dat je niet "Component" maar "Control" moet hebben?

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:45
Hoe doet een BackgroundWorker het ?
Ik heb iig geen idee of een BackgroundWorker het zowel in WinForms als in WPF goed afhandeld, maar je zou eens kunnen kijken hoe die werkt ?

Volgens mij doet een BackgroundWorker het via een SynchronizationContext, en dat zou imho / afaik zowel in Winforms als WPF goed moeten gaan.
Echter, je moet dan wel uw SynchronizationContext gaan doorgeven denk ik.

Dit is trouwens een interessant artikel:
http://blogs.msdn.com/b/k...chronizationcallback.aspx
Hier werd een SynchronizationCallback gemaakt, die iedere keer de Current SynchronizationContext zet, indien nodig, zodanig dat je in uw background thread altijd over de juiste synchronizationcontext beschikt.

[ Voor 29% gewijzigd door whoami op 29-11-2011 14:43 ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
RobIII schreef op dinsdag 29 november 2011 @ 12:36:
[...]

Even een gokje: Ik denk dat je niet "Component" maar "Control" moet hebben?
Dat lijkt me eigenlijk niet, een Component kan ik me nog in vinden, maar dit is toch echt geen Control, dat zou wel raar zijn denk ik (Control is trouwens wel heel erg Winforms georienteerd). Ik had het idee om een Control te gebruiken echter al eerder. Mijn idee was als volgt: het cross-threading probleem treedt op omdat de UI controls die ik wil gebruiken in een andere thread zijn gemaakt dan mijn background thread. Dit kun je verhelpen door een Invoke aan te roepen op die controls, de method die je dan invoked wordt op de thread waarop die control is gemaakt aangeroepen.

Mijn idee was dan om gewoon een instantie van Control binnen mijn SdkWrapper aan te maken, in de constructor (dit is dan nog dezelfde thread als de UI), en daarop Invoke aan te roepen. Dat werkt echter niet omdat je control eerst een window handle moet hebben (ofzoiets, ik weet de error niet meer uit m'n hoofd). Het komt erop neer denk ik dat je control ook echt in een window moet zitten (een Handle moet hebben) voordat je kan Invoken.

Ik neem aan dat hetzelfde probleem zal optreden als ik van Control overerf, dus dat zal ook niet gaan werken.


Ik zal eens kijken naar de background worker, die lijkt inderdaad hetzelfde probleem op te lossen... Nu alleen hopen dat die oplossing niet winforms specifiek is...

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
NickThissen schreef op dinsdag 29 november 2011 @ 08:53:
Hoezo zou er meer dan een UI thread zijn?
Ik bedoelde meer "hoe weet je dat er maar 1 UI Thread is?" Dit is eigenlijk een type logica dat niet direct in jouw class thuishoort. Het zou beter zijn als de gebruiker zelf met threads om kan gaan. ;)
Hoogie2004 schreef op dinsdag 29 november 2011 @ 08:57:

@Nick, ik weet niet of je dit echt netjes kan oplossen. De netste manier is misschien nog wel simpelweg 2 (of 3) class-libraries opleveren, 1 voor WPF, 1 voor Winforms en 1 zonder iets...
De 'beste' oplossing staat er direct boven, dat is namelijk die SynchronizationContext. Misschien nog even een constructor toevoegen waarbij je expliciet kan aangeven welke context je wil gebruiken (of null voor geen context, bijv. bij een consoleapplicatie).

Overigens bevat coderegel 25 in de TS een mogelijk probleem als DataUpdated op null wordt gezet in een andere thread.
whoami schreef op dinsdag 29 november 2011 @ 13:07:
Volgens mij doet een BackgroundWorker het via een SynchronizationContext, en dat zou imho / afaik zowel in Winforms als WPF goed moeten gaan.
Klopt, zie \[C#] Nested backgroundworkers geven out-of-order resultaten

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
pedorus schreef op dinsdag 29 november 2011 @ 14:42:
[...]

Ik bedoelde meer "hoe weet je dat er maar 1 UI Thread is?" Dit is eigenlijk een type logica dat niet direct in jouw class thuishoort. Het zou beter zijn als de gebruiker zelf met threads om kan gaan. ;)
Een gebruiker die zelf met threads om kan gaan is niet mijn doelgroep eigenlijk. De bedoeling van deze code is om hem te gebruiken zoals in mijn tweede stukje code. Als iemand hem op een andere manier gaat gebruiken met meerdere threads ofzo, ja, zo krijg je alles wel kapot, daar is het niet voor bedoeld.
Als iemand met threads kan werken kan hij beter de originele C# implementatie gebruiken (waar mijn code nog wat bovenop bouwt, juist om NIET met threads te hoeven werken).
pedorus schreef op dinsdag 29 november 2011 @ 14:42:
[...]
Overigens bevat coderegel 25 in de TS een mogelijk probleem als DataUpdated op null wordt gezet in een andere thread.
Je bedoelt dat DataUpdated door een andere thread op null wordt gezet precies tussen de null check en het aanroepen door? Dat zou ik dan moeten oplossen door deze code te 'sync locken' ofzo? Daar heb ik niet echt ervaring mee, heb je misschien een voorbeeldje? Ook al lijkt me dit ook totaal niet van belang, juist omdat er altijd gewoon maar een instantie van SdkWrapper zal zijn in een enkele thread (de UI thread).


Die SynchronizationContext zal ik eens naar kijken (gebruikt de BackgroundWorker dat ook?)

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
NickThissen schreef op dinsdag 29 november 2011 @ 14:50:
[...]

Een gebruiker die zelf met threads om kan gaan is niet mijn doelgroep eigenlijk. De bedoeling van deze code is om hem te gebruiken zoals in mijn tweede stukje code.
Dan lijkt mij een control (zoals de Backgroundworker die ook heeft) die je op een formpje kan slepen nog gebruikersvriendelijker.
Je bedoelt dat DataUpdated door een andere thread op null wordt gezet precies tussen de null check en het aanroepen door? Dat zou ik dan moeten oplossen door deze code te 'sync locken' ofzo? Daar heb ik niet echt ervaring mee, heb je misschien een voorbeeldje? Ook al lijkt me dit ook totaal niet van belang, juist omdat er altijd gewoon maar een instantie van SdkWrapper zal zijn in een enkele thread (de UI thread).
Zonder synchronisatie vuurt het in je eigen, aparte thread. De oplossing is echter erg simpel (variabele gebruiken).
http://stackoverflow.com/...-events-and-thread-safety
http://stackoverflow.com/...-handlers-not-thread-safe
Die SynchronizationContext zal ik eens naar kijken (gebruikt de BackgroundWorker dat ook?)
(Ja.)

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
Hmmm... Ik heb het aan het werk gekregen. Het was een stuk makkelijker dan ik dacht (en voor zover ik in dotPeek kan zien werkt een BackgroundWorker niet met een SynchronizationContext?).

Ik declareer gewoon een SynchronizationContext en zet deze gelijk aan SychronizationContext.Current in de constructor. Daarna invoke ik de OnDataUpdate method (degene die het event raised) via SynchronizationContext.Post, en klaar! Lijkt prima te werken in winforms en WPF :)

Zijn hier verder nog problemen mee?
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
        private SynchronizationContext syncObject;

        public SdkWrapper()
        {
            syncObject = SynchronizationContext.Current;
        }

        private void Loop()
        {
            while (this.IsRunning)
            {
                i++;
                var e = new DataEventArgs {Data = i.ToString()};

                syncObject.Post(RaiseDataUpdated, e);

                Thread.Sleep(50);
            }
        }

        private void RaiseDataUpdated(object e)
        {
            this.OnDataUpdated((DataEventArgs) e);
        }

        protected virtual void OnDataUpdated(DataEventArgs e)
        {
            if (DataUpdated != null) DataUpdated(null, e);
        }


In ieder geval bedankt voor de hulp... :)

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:45
Wat als SynchronizationContext.Current null is ?

Waarom 'null' gebruiken als sender bij het raisen van DataUpdated ?

Wat is het nut van die RaiseDataUpdated method ? Je kan toch rechtstreeks OnDataUpdated gaan aanroepen; of mis ik iets ?

Mogelijke race-conditie in OnDataUpdated (wat Pedorus ook al aanhaalde).

[ Voor 16% gewijzigd door whoami op 29-11-2011 16:21 . Reden: Hadden we hier vroeger geen [c ] tag ? voor 'inline code' ? ]

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
whoami schreef op dinsdag 29 november 2011 @ 16:16:
Wat als SynchronizationContext.Current null is ?
Goeie, dat moet ik even checken.
whoami schreef op dinsdag 29 november 2011 @ 16:16:Waarom 'null' gebruiken als sender bij het raisen van DataUpdated ?
Dat is een overblijfsel van de code van evolution536 hierboven (die methode had ik static gemaakt waardoor 'this' niet meer kan), had ik al aangepast nadat ik dit gepost had.
whoami schreef op dinsdag 29 november 2011 @ 16:16:Wat is het nut van die RaiseDataUpdated method ? Je kan toch rechtstreeks OnDataUpdated gaan aanroepen; of mis ik iets ?
SynchronizationContext.Post verwacht een methode met een object als parameter. Nu kan ik OnDataUpdated ook een object laten verwachten maar dat is niet helemaal netjes (en niet 'standaard' voor een protected virtual method die een event raised), vandaar maar een tussen stapje.
whoami schreef op dinsdag 29 november 2011 @ 16:16:
Mogelijke race-conditie in OnDataUpdated (wat Pedorus ook al aanhaalde).
Ok, dus ik moet DataUpdated eerst aan een lokale variabele toekennen en die gebruiken?
C#:
1
2
var x = this.DataUpdated;
if (x != null) x(this, e);

zoiets?

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • evolution536
  • Registratie: Maart 2009
  • Laatst online: 05-06-2024

evolution536

besh besh

Ik heb mijn projectje in delen teruggevonden, en kwam tot de conclusie dat mijn programma geen thread vanuit .NET gebruikte. Ik gebruikte een persistent hook die in Win32 haakte. hiermee een delegate, callback etc.

Ik denk dat het daarom bij jouw niet werkte. Mijn excuses voor de eventuele breinkrampen die je ervoor kreeg :+

Kijk wellicht hier is naar:

System.ASyncCallback

Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:45
NickThissen schreef op dinsdag 29 november 2011 @ 17:32:
[...]
SynchronizationContext.Post verwacht een methode met een object als parameter. Nu kan ik OnDataUpdated ook een object laten verwachten maar dat is niet helemaal netjes (en niet 'standaard' voor een protected virtual method die een event raised), vandaar maar een tussen stapje.
Waarom zou dat niet netjes zijn ?
Jouw EventArgs instance is ook een object; je kan dus prima die EventArgs instance passen aan die Post method.
Ok, dus ik moet DataUpdated eerst aan een lokale variabele toekennen en die gebruiken?
C#:
1
2
var x = this.DataUpdated;
if (x != null) x(this, e);

zoiets?
ja.

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
whoami schreef op dinsdag 29 november 2011 @ 21:10:
[...]
Waarom zou dat niet netjes zijn ?
Het patroon wat in .NET normaal gebruikt wordt voor events is een methode genaamd On<eventnaam>, die het EventArgs als argument meekrijgt. Deze maak je dan virtual zodat mensen hem kunnen overriden mocht dat van toepassing zijn. Ik volg dat patroon graag omdat het gewoon makkelijk is en soms ook wel toepassing heeft. Als ik die methode nu een Object ga laten accepteren dan werkt dat dus niet meer met overriden (nou ja, dan zou ik gaan moeten casten en je weet dan nooit zeker of je wel een EventArgs type krijgt).
whoami schreef op dinsdag 29 november 2011 @ 21:10:
[...]
Jouw EventArgs instance is ook een object; je kan dus prima die EventArgs instance passen aan die Post method.
Ja, maar een delegate naar een method die een DataEventArgs accepteert is niet hetzelfde als een delegate naar een method die een object accepteert. Om het in Visual Studio error termen uit te drukken:
void (DataEventArgs) cannot be assigned to void (object).

De Post method verwacht een SendOrPostCallback delegate, en die heeft een parameter van type object, en die signature moet ik gewoon matchen.

Ik kan een method die DataEventArgs accepteert dus niet toekennen (of meegeven) aan een parameter die een method met een object verwacht. De DataEventArgs zelf kan ik inderdaad prima meegeven als parameter, maar de methode (de delegate eigenlijk denk ik, die terminologie ligt me nog niet altijd even lekker) kan ik niet meegeven.
Vandaar dus de tussen stap.

[ Voor 7% gewijzigd door NickThissen op 29-11-2011 21:53 ]

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:45
NickThissen schreef op dinsdag 29 november 2011 @ 21:51:
[...]

Het patroon wat in .NET normaal gebruikt wordt voor events is een methode genaamd On<eventnaam>, die het EventArgs als argument meekrijgt. Deze maak je dan virtual zodat mensen hem kunnen overriden mocht dat van toepassing zijn. Ik volg dat patroon graag omdat het gewoon makkelijk is en soms ook wel toepassing heeft. Als ik die methode nu een Object ga laten accepteren dan werkt dat dus niet meer met overriden (nou ja, dan zou ik gaan moeten casten en je weet dan nooit zeker of je wel een EventArgs type krijgt).
Maar, waarom zou je die method een object laten accepteren ? Dat hoeft niet.
Ja, maar een delegate naar een method die een DataEventArgs accepteert is niet hetzelfde als een delegate naar een method die een object accepteert. Om het in Visual Studio error termen uit te drukken:
void (DataEventArgs) cannot be assigned to void (object).

De Post method verwacht een SendOrPostCallback delegate, en die heeft een parameter van type object, en die signature moet ik gewoon matchen.

Ik kan een method die DataEventArgs accepteert dus niet toekennen (of meegeven) aan een parameter die een method met een object verwacht. De DataEventArgs zelf kan ik inderdaad prima meegeven als parameter, maar de methode (de delegate eigenlijk denk ik, die terminologie ligt me nog niet altijd even lekker) kan ik niet meegeven.
Vandaar dus de tussen stap.
C#:
1
2
3
4
context.Post  (new SendOrPostCallback (delegate (object o ) 
                     {
                           OnDataUpdated(eventArgs);
                     }, null);

/uit de losse pols, dus er kunnen wel foutjes in zitten.

En, om het helemaal mooi te maken, maak je een extension method op EventHandler<EventArgs>, die er als volgt uit ziet:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static void Raise<TEventArgs>( this EventHandler<TEventArgs> target, 
    SynchronizationContext context, 
    TEventArgs args ) where TEventArgs : EventArgs
{

    context.Post (new SendOrPostCallback (delegate ( object o )
    {
        EventHandler<TEventArgs> handler = target;

        if( handler != null )
        {
            handler (sender, e);
        }

    }), null);

}


En dan doe je in uw code dit:

C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void Loop()
{
    while (this.IsRunning)
    {
        i++;
        var e = new DataEventArgs {Data = i.ToString()};

        OnDataUpdated (e);

        Thread.Sleep(50);
    }
} 


protected virtual void OnDataUpdated( DataEventArgs e )
{
    DataUpdated.Raise (syncObject, this, e);
}

https://fgheysels.github.io/


Acties:
  • 0 Henk 'm!

  • pedorus
  • Registratie: Januari 2008
  • Niet online
NickThissen schreef op dinsdag 29 november 2011 @ 16:09:
Hmmm... Ik heb het aan het werk gekregen. Het was een stuk makkelijker dan ik dacht (en voor zover ik in dotPeek kan zien werkt een BackgroundWorker niet met een SynchronizationContext?).
Ligt eraan hoe je dat ziet. BackgroundWorker maakt gebruik van AsyncOperationManager.CreateOperation(), welke een nieuwe SynchronizationContext.Current aanmaakt als die null is, en die SynchronizationContext.Current opslaat in een AsyncOperation. Persoonlijk maak ik dan liever geen 'lege' SynchronizationContext aan (misschien wordt de thread later als bijv. forms thread gebruikt), en zie ik ook het voordeel van het AsyncOperation-schilletje niet echt in. :p

Vitamine D tekorten in Nederland | Dodelijk coronaforum gesloten


Acties:
  • 0 Henk 'm!

  • RobIII
  • Registratie: December 2001
  • Niet online

RobIII

Admin Devschuur®

^ Romeinse Ⅲ ja!

(overleden)
Sterk; ik kom net dit tegen van James Michael Hare (a.k.a. @BlkRabbitCoder). Hoewel dit (nog) niet specifiek over het raisen op een UI Thread gaat verwacht ik wel dat dat er aan gaat komen hem "kennende" (of kaart 't even aan :P ). Desalniettemin leuk artikeltje wat wel het vermelden waard was dacht ik zo.

[ Voor 15% gewijzigd door RobIII op 02-12-2011 09:58 ]

There are only two hard problems in distributed systems: 2. Exactly-once delivery 1. Guaranteed order of messages 2. Exactly-once delivery.

Je eigen tweaker.me redirect

Over mij


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 09-09 10:50
whoami schreef op dinsdag 29 november 2011 @ 22:18:
[...]
Maar, waarom zou je die method een object laten accepteren ? Dat hoeft niet.


[...]


C#:
1
2
3
4
context.Post  (new SendOrPostCallback (delegate (object o ) 
                     {
                           OnDataUpdated(eventArgs);
                     }, null);

/uit de losse pols, dus er kunnen wel foutjes in zitten.
Ja, maar het enige wat je nu doet is mijn extra methode in een anonymous method veranderen. Is verder toch precies hetzelfde? Komt gewoon op hetzelfde neer, alleen probeer ik dingen als anynomous methods te vermijden hier omdat het handig is als andere mensen de code begrijpen, die misschien geen idee hebben wat een delegate is.
RobIII schreef op vrijdag 02 december 2011 @ 09:47:
Sterk; ik kom net dit tegen van James Michael Hare (a.k.a. @BlkRabbitCoder). Hoewel dit (nog) niet specifiek over het raisen op een UI Thread gaat verwacht ik wel dat dat er aan gaat komen hem "kennende" (of kaart 't even aan :P ). Desalniettemin leuk artikeltje wat wel het vermelden waard was dacht ik zo.
Ziet er leuk uit ja, ga het zo eens lezen, bedankt!

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • whoami
  • Registratie: December 2000
  • Laatst online: 17:45
De reden waarom ik het in die anonymous delegate zou veranderen, is gewoon duidelijkheid.
Nu heb jij een RaiseDataUpdated method en een OnDataUpdated method.
IMHO verwarrend, want welke method moet je nu ook alweer aanroepen ... Mijn oplossing neemt dat weg.

https://fgheysels.github.io/

Pagina: 1