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:
En mensen gebruiken hem zo:
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!
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!