Laden...

MVVM Aktualisierung von Eigenschaften durch Task.Factory

Erstellt von Abt vor 8 Jahren Letzter Beitrag vor 2 Jahren 2.617 Views
Hinweis von Abt vor 8 Jahren

Der Beitrag wurde ursprünglich in [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) hinzugefügt.
Habe mich entschieden ihn doch abzuteilen und evtl. vorher zu besprechen.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren
MVVM Aktualisierung von Eigenschaften durch Task.Factory

Ich hab noch einen Weg gefunden die WPF GUI MVVM-getreu aktualisieren zu können, also wenn man durch das ViewModel keinen Zugriff auf den Dispatcher hat:


public class MyViewModel : INotifyPropertyChanged
    {
        private readonly TaskFactory _uiTaskFactory;
        private readonly IAnyExternalService _service;

        public MyViewModel( IAnyExternalService service )
        {
            _uiTaskFactory = new TaskFactory( TaskScheduler.FromCurrentSynchronizationContext() );
            _service = service;

            // Beispielhaft bietet der Service ein Event, dessen Resultat wir an das ViewModel übertragen wollen
            // Der Service läuft aber in einem anderen Thread
            _service.OnConnected += ( obj, args ) =>
            {
                // Task im synchronisierten Context (GUI) starten
                _uiTaskFactory.StartNew( () =>
                {
                    // Wert übertragen
                    this.State = args.StateName;
                } );
            };
        }

        private string _state;
        public string State
        {
            get { return _state; }
            set
            {
                _state = value;
                // Nofify Property Changed Implementierung hier
            }
        }
    }


Klasse ist nur beispielhaft

5.657 Beiträge seit 2006
vor 8 Jahren

Hi Abt,

wozu braucht man das? Oder anders gefragt, welches Problem besteht in dem Beispiel?

Normalerweise würde ich davon ausgehen, daß sich der DataBinding-Mechanismus um alles kümmert.

Christian

Weeks of programming can save you hours of planning

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Konkret geht es um folgende auch die beispielhaft gezeigte implementierung.
Würde ich Task.Factory im entsprechenden Context nicht nutzen, dann wird eine entsprechende Exception geworfen.

Fehlermeldung:
Additional information: The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

Grund: der Event wird eben in einem anderen Thread ausgeführt; und irgendwie muss ich ja in den korrekten Context kommen.
Ich hab in ViewModel aber keinen Dispatcher (ausser ich hole ihn mir über Dispatcher.CurrentDispatcher oder ähnliches, was sicherlich nicht konform ist.

Die FAQ; und das ist der Grund, weshalb ich es dort aufgeführt hatte - beachtet das gar nicht.

In meinem konkreten Fall handelt es sich um eine Windows 10 App, die mit einem Microsoft Band2 kommuniziert und das SDK verwendet.
Ich seh jetzt nicht wirklich (oder ich übersehe es), wo das der Binding-Mechanismus automatisch übernehmen würde.

5.299 Beiträge seit 2008
vor 8 Jahren

ist das nicht ein bischen böse zu den Resourcen, wenn jedes Viewmodel seine eigene TaskFactory mit sich rumschleppt?

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Finde ich jetzt weniger böse als nen Dispatcher rein zu stopfen 😉
Du kannst bei Bedarf die TaskFactory wrappen und injecten, dann haste nur einen. Aber wollte hier ja die grundlegende Funktionsweise zeigen; keinen Service zur Verfügung stellen.

Kannst die Methode

 _uiTaskFactory.StartNew( () =>
                {
                    // Wert übertragen
                    this.State = args.StateName;
                } );

gerne

 _uiContextService.ExecuteInUIContext( () =>
                {
                    // Wert übertragen
                    this.State = args.StateName;
                } );

nennen 😃

5.299 Beiträge seit 2008
vor 8 Jahren

ich verstehe nicht, oder du 😉

Ich meine, jede Viewmodel-Instanz erhält ein neues (new) TaskFactory-Objekt. Ich weiß nicht, wie fett diese Factories sind, und obs was ausmacht, wenn man 10000 Datensätze lädt, dass damit dann auch 10000 TaskFactory-Objekte erstellt sind.

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Ja, weiß was Du meinst.


 public MyViewModel( IAnyExternalService service, IMyFactoryWrapper wrapper )
        {


Wrap Deine Factory mit einem IMyFactoryWrapper Service, lass eine einzige Instanz davon existieren, injecte sie via DI und jut is. =)

5.299 Beiträge seit 2008
vor 8 Jahren

hmm - ich dachte, das sei hier für eine FAQ vorgesehen, also gedacht als Hilfe für weniger fortgeschrittene.
Aber ob die mit Fach-Chinesisch wie "Wrap Deine Factory mit einem IMyFactoryWrapper Service, lass eine einzige Instanz davon existieren, injecte sie via DI und jut is." was anzufangen wissen? 🤔

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Finds schade (und etwas wirr), dass Du das jetzt in den falschen Hals rein bekommst.
Eben wegen der Zielgruppe habe ich nicht von Anfang an den DI-Container verwendet, sondern die Factory im Konstruktor erstellt.

Das Wrappen ist entstanden, da Du die Ressourcen"verschwendung" (und indirekt die Einfachheit) bemängelt hattest. Und die Ausdruckweise war so gewählt, da ich Dich als fortgeschrittenen Entwickler angenommen hatte und Du dies verstehst.
Das DI-Beispiel würde ich gar nicht in die FAQ mit aufnehmen - es war nur eine Antwort und die Lösung auf Deine Ressourcen-Kritik.

F
10.010 Beiträge seit 2004
vor 8 Jahren

Das DI-Beispiel würde ich gar nicht in die FAQ mit aufnehmen - es war nur eine Antwort und die Lösung auf Deine Ressourcen-Kritik.

Was ich schade finden würde, denn so wird genau das passieren was ErfinderDesRades angenommen hat.
Auch die FAQ sollten immer und in jedem Fall der best Praxis entsprechen.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Also erst mal gings mit ja um den Lösungsweg an für sich, den ich - ich seh bisher nur Vorteile, keine Nachteile - in die FAQ aufnehmen würde.
Wir können das dann gerne als Basisumsetzung sowie als Umsetzung via DI aufzeigen.

Wir bewegen uns irgendwie von der Kernsache weg...

5.299 Beiträge seit 2008
vor 8 Jahren

nanU?
Ich hätte diese 2 Punkte für die kernsache gehalten:

  1. Zu diskutieren, wie die best practice sei, und da hab ich vermutet, dass deine Lösung, so, wie sie in post#1 steht, unnötig viel Speicher belegt.
  2. Auch die Gestaltung, sodass auch Anfänger von der FAQ profitieren.

Was wäre in deinen Augen die Kernsache?

(Ich trau mich kaum, es abzuschicken, denn zu diskutieren, was die Kernsache ist, ist ganz sicher nicht die Kernsache.
Meinetwegen kannste meine Posts auch rauslöschen, wenn das hilft, den Thread wieder mehr @Topic zu bringen.

(Was ich noch nur ungern zugebe: )
Ausserdem könnte ich tatsächlich kein DI umsetzen. Ich weiß zwar im groben, was das ist, aber alle meine Versuche einer praktischen Test-Umsetzung führten zu mehr Uständlichkeit statt zu weniger.
Wäre jetzt ein neuer Anlauf, wenn mir hier jetzt was praktikables präsentiert würde.

(Auch hierfür fürchte ich, harsche Ablehnung zu erfahren: )
Zur Gestaltung hätte ich noch die Bitte um ein lauffähiges Code-Sample. Ist für mich ein "Tutorial-Prinzip": Solange ich die Tutorial-Aussage nicht in Code umgesetzt auf meiner Platte habe, solange ist das Tut nichts als unbewiesene Worte.
)(Klammer zu 😉 )

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Also der funktionierende Code ist https://github.com/BenjaminAbt/MicrosoftBand2HeartRateApp/blob/master/src/BandApp/ViewModels/HeartRateViewModel.cs

Es würde wahrscheinlich 5 Minuten brauchen selbst eine entsprechende Beispielumsetzung zu realisieren, wenn hier die App nicht ausführen kann.
Wenn es um das "Beweisen" geht......

Und die Kernsache ist für mich die TaskFactory im entsprechend synchronisierten Kontext statt ein Dispatcher.
DI ist dann Beiwerk. Wobei das injezieren von Abhängigkeiten (über den Konstruktor) ohnehin nur zum Lösen von harten Abhängigkeiten ist und keine Pflicht DI zu nutzen, aber das ist ein anderes Thema.

5.299 Beiträge seit 2008
vor 8 Jahren

ich bin iwie zu dumm zum Downloaden - gibts da einen Sample-Solution-Zip?

Ich sehe da nur im Browser präsentiertes c#, müsste ich auskopieren, und überhaupt erst ein Sample-Projekt drumrum erstellen, was den Fehler reproduziert.

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Wie immer auf GitHub zuerst auf die Projekteseite (hier https://github.com/BenjaminAbt/MicrosoftBand2HeartRateApp) und dann rechts auf Download Zip.

5.299 Beiträge seit 2008
vor 8 Jahren

ah, danke.

kann ich aber aufgrund jedes der Requirements nicht öffnen:

Visual Studio 2015 Update 1  
Microsoft Band 2 SDK (integrated as NuGet Package)  
Microsoft Band (1 or 2) or FakeBand

Der frühe Apfel fängt den Wurm.

5.299 Beiträge seit 2008
vor 8 Jahren

Es würde wahrscheinlich 5 Minuten brauchen selbst eine entsprechende Beispielumsetzung zu realisieren, wenn hier die App nicht ausführen kann. (OT: Solche Bemerkungen kriegen es immer wieder hin, dass ich das Gefühl hab, ich soll mich dumm fühlen.
Du schaffst es sicherlich in 5 Minuten, ich aber vermutlich nicht. Allein die fehlerwerfenden Funktionalität - das müsste ich erst konzipieren, aufsetzen, testen, korrigieren...)

Wenn es um das "Beweisen" geht...... Bingo! 👍 - es geht genau ums Beweisen.
Und ich sehe immer den Autor einer FAQ/Tutorial in der Beweispflicht, nicht den Leser.
Und bewiesen ist erst, wenns auf meinem System funktioniert, wie erwartet, und der Code auch sonst Tut-Aussagen bestätigt.

Beweis-Suche ist bei mir automatische Reaktion, und das hat mich tatsächlich erstmal von abgehalten, deinen Kernpunkt aufzufassen (ich kann halt nur eins nachm andern)

Und die Kernsache ist für mich die TaskFactory im entsprechend synchronisierten Kontext statt ein Dispatcher. Also pros und cons, die TaskFactory zu nehmen, wenn kein Dispatcher gegeben.

Da fällt mir tatsächlich was zu ein, sogar trotz misserfolgter Beweissuche - ist aber auch nix wirklich originelles:
pro:
Es funktioniert (nehme ich mal an). Das ist zunächstmal das entscheidende

con:
vermutlich wesentlich langsamer als Dispatcher.BeginInvoke. Ich hab bisserl IL gespickelt, Dispatcher macht was ganz anderes als TaskFactory.

Schlussfolgerung:1.Wenn verfügbar, immer den Dispatcher nehmen 1.vlt. kann man sich noch iwas ausdenken, einen Dispatcher iwie doch noch herzukriegen
Ich jdfs. akzeptiere nicht unbesehen, dass das ganz unmöglich ist, weil zumindest im Gui isser ja da.
Ist halt die Herausforderung, es mit möglichst wenig Kopplung hinzukriegen, aber für solch gibts ja einige Mechanismen und Pattern.

1.Testen.
Wie schlimm ist der Performance-Nachteil?

Der frühe Apfel fängt den Wurm.

T
314 Beiträge seit 2013
vor 8 Jahren

(Was ich noch nur ungern zugebe: )
Ausserdem könnte ich tatsächlich kein DI umsetzen. Ich weiß zwar im groben, was das ist, aber alle meine Versuche einer praktischen Test-Umsetzung führten zu mehr Uständlichkeit statt zu weniger.
Wäre jetzt ein neuer Anlauf, wenn mir hier jetzt was praktikables präsentiert würde.

Das wäre doch noch was für die FAQ oder eher vielleicht als Artikel? 😃

Ich vermute mal, soweit ich dies nachvollziehen konnte, liegt der Hauptgrund warum man dies z.B. über einen Service nutzt in der Testbarkeit?

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Natürlich ist es die Testbarkeit. Es ist aber auch die Wiederverwendbarkeit. Ich kann zB kein Dispatcher verwenden, wenn ich das ViewModel eben nicht nur in WPF verwende, sondern zB. auch in einer anderen Technologie.
Die Art und Weise des Dispatchers ist doch eine harte Bindung an WPF und untergräbt in meinen Augen der Sinn und Zweck eines MVVM ein dieser Stelle und in diesem Bezug massiv.

(OT: Solche Bemerkungen kriegen es immer wieder hin, dass ich das Gefühl hab, ich soll mich dumm fühlen.

War zu keiner Zeit Sinn und Zweck.
Die 5 Minuten sind - wie bei solch einem Satz üblich - als Indikator für die Einfachheit der Nachvollziehbarkeit zu werten; nicht in einer totalen Aussage.
Muss man denn bei jedem Satz aufpassen, was man sagt....? 🤔 Also....

Man sieht doch schon am gesamten Quellcode, dass das keine Welt ist das Ding in einer eigenen App nachzuvollziehen, wenn man kein VS2015 hat.
Das sind ~30 Zeilen Code?! Also irgendwie - sorry - mal auf dem Boden bleiben. =)

Und da fängt es schon an

Schlussfolgerung:
Wenn verfügbar, immer den Dispatcher nehmen

Seh ich schon nicht so. Ich würde es "wenn notwendig, dann den Dispatcher nehmen" ausdrücken.
Das Ding ist halt NUR in der WPF GUI da. Aber.. entwickle ich ein ViewModel immer nur für WPF?
Soviel zum Thema "Perfektes Beispiel für die FAQ"... um Deine Argumente zu nehmen 😉

Und daher hätte ich auch gern Feedback von WPF Profis, bevor das in die FAQ kommt - eben auch in Bezug auf Erfahrung zum Dispatcher, Wiederverwendbarkeit, Testbarkeit, Modularisierung etc etc. Das, was eben 'ne moderne Software (mit) ausmacht.

Mir ist völlig schleierhaft, wieso wir jetzt Dinge diskutieren, die weder notwendig noch zielführend sind.

3.003 Beiträge seit 2006
vor 8 Jahren

Aus Neugier - spricht was dagegen, den Service nicht so eng an das vm zu binden, sondern ihm einen Delegaten zum Aktualisieren eines Wertes (hier: State) unterzuschieben?


public class MyViewModel : INotifyPropertyChanged
    {
        private readonly TaskFactory _uiTaskFactory;

        public MyViewModel( IAnyExternalService service )
        {
            _uiTaskFactory = new TaskFactory( TaskScheduler.FromCurrentSynchronizationContext() );
            //Call setzt im service die Aktualisierungsmethode
            service.Call(_uiTaskFactory.StartNew(() => service.Update(State)));
            
        }
        //Property State wie gehabt
    }


LaTino

"Furlow, is it always about money?"
"Is there anything else? I mean, how much sex can you have?"
"Don't know. I haven't maxed out yet."
(Furlow & Crichton, Farscape)

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Finde nicht, dass der Service dafür verantwortlich sein sollte oder gar kann.
Der kennt a) die Umstände nicht oder b) könnte durch einen Dritten kommen und unveränderlich sein.

W
955 Beiträge seit 2010
vor 8 Jahren

Ich kann zB kein Dispatcher verwenden, wenn ich das ViewModel eben nicht nur in WPF verwende, sondern zB. auch in einer anderen Technologie.
Die Art und Weise des Dispatchers ist doch eine harte Bindung an WPF und untergräbt in meinen Augen der Sinn und Zweck eines MVVM ein dieser Stelle und in diesem Bezug massiv. Hi,
dafür gibt es den SynchronizationContext der den jeweiligen GUI-Kontext abstrahiert.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

dafür gibt es den
>

Genau das verwendet ja "meine" Factory (nur eben ohne den Dispatcher) auch 😉

Mit der Suche nach fand ich aber auch das, was ich als nützliche Umsetzung ohne die Abhängigkeit auf den Dispatcher empfinde:

public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
    private SynchronizationContext SynchronizationContext;

    public ThreadSafeObservableCollection()
    {
        SynchronizationContext = SynchronizationContext.Current;

        // current synchronization context will be null if we're not in UI Thread
        if (SynchronizationContext == null)
            throw new InvalidOperationException("This collection must be instantiated from UI Thread, if not, you have to pass SynchronizationContext to con                                structor.");
    }

    public ThreadSafeObservableCollection(SynchronizationContext synchronizationContext)
    {
        if (synchronizationContext == null)
            throw new ArgumentNullException("synchronizationContext");

        this.SynchronizationContext = synchronizationContext;
    }

    protected override void ClearItems()
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.ClearItems()), null);
    }

    protected override void InsertItem(int index, T item)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.InsertItem(index, item)), null);
    }

    protected override void RemoveItem(int index)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.RemoveItem(index)), null);
    }

    protected override void SetItem(int index, T item)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.SetItem(index, item)), null);
    }

    protected override void MoveItem(int oldIndex, int newIndex)
    {
        this.SynchronizationContext.Send(new SendOrPostCallback((param) => base.MoveItem(oldIndex, newIndex)), null);
    }
}

Müsste man nur noch für die allgemeine Verwendung anpassen.
Hätte den Vorteil, dass man das asynchrone Verhalten nicht hat, wenn man es nicht braucht.

5.299 Beiträge seit 2008
vor 8 Jahren

echt - da war SynchronisizeContext eingebaut?

Dann ist gut möglich dasses recht sauber ist, denn in WinForms geht Control.BeginInvoke intern auch über genau diesen SynchronisizeContext.
Und die SynchronisizeContext-Choose ist sauber nachm Strategy-Pattern aufgezogen, um verschiedene Präsentationstechnologien entkoppeln zu können.

Trotzdem ist gut möglich, dass ein Dispatcher deutlich bessere Leistung bringt - zumindest in WinForms muss man sich ja nicht grade überschlagen vor Begeisterung.

Der frühe Apfel fängt den Wurm.

W
955 Beiträge seit 2010
vor 8 Jahren

... und hier ist noch eine Erweiterungsmethode SendAsync von Stephen Toub die asynchron warten kann.

5.299 Beiträge seit 2008
vor 8 Jahren

So, jetzt reden wir glaub über 3 oder 4 verschiedene Dinge, und keins davon kann ich testen - weiß nicht, obs euch da anders geht.

Also falls jemand sich die Mühe gemacht hat, eine Test-Solution für diese Fragen zu erstellen, wäre ich zumindest dankbar (ich weiß nicht, wie's den anderen geht), wenner die Sources hier anhängen könnte.

Der frühe Apfel fängt den Wurm.

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

echt - da war SynchronisizeContext eingebaut?.

_uiTaskFactory = new TaskFactory( TaskScheduler.FromCurrent**:::{style="color: red;"}SynchronizationContext){red}**() );

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 8 Jahren

Gibt es Vorschläge - gerne auch von anderen - wie wir nun unsere FAQ in diesem Hinblick verbessern können?
Aktuell ist es leider nicht abgedeckt.

D
23 Beiträge seit 2007
vor 2 Jahren

Hallo Abt

Ich habe da ein Server-Client-Projekte zur Steuerung von Licht und verschiedenen elektronischen Geräten - auch als Smarthome bekannt.
Es läuft auf Win 10 und Android absolut fehlerfrei, nur beim Raspberry Pi 3 mit WinIoT bekomme ich Ausnahmen gemeldet. Ich bin mir nicht sicher wo genau der Fehler ist, da es teilweise funktioniert.
Bsw. das bringt keine Ausnahme:


if (house_Start.ServerStatus)
                    Device.StartTimer(TimeSpan.FromSeconds(1), () => OnTimerTick());


private bool OnTimerTick()
        {
            Device.BeginInvokeOnMainThread(async () =>
            {
                if (house_Start.ServerStatus)
                    serverLabel.Text = "Server ist offline";
                else if (!house_Start.ClientStatus)
                    serverLabel.Text = "Client nicht gestartet";
                else
                    serverLabel.Text = "Server ist online";
            });
            return true;
        }

nur hier scheint es bei der zusammenführung der verschiedenen Tasks zu geben:


private void UpdateLampen()
        {
            Device.StartTimer(TimeSpan.FromMilliseconds(10), () => UpdateLabel());
        }


private bool UpdateLabel()
        {
            if (j < 32 && LampenLabel[j].BackgroundColor == Color.Red && Lampen[j])
                DispatcherHelper.CheckBeginInvokeOnUI(()=>
                //Dispatcher.BeginInvokeOnMainThread(() =>
                {
                    LampenLabel[0].BackgroundColor = Color.Green;
                    j++;
                });
            else if (j < 32 && LampenLabel[j].BackgroundColor == Color.Green & !Lampen[j])

                DispatcherHelper.CheckBeginInvokeOnUI(() =>
                //Dispatcher.BeginInvokeOnMainThread(() =>
                {
                    LampenLabel[j].BackgroundColor = Color.Red;
                    j++;
                });
            else
                j++;
            if (j > 31)
            {
                j = 0;
                return false;
            }
            else
                return true;
        }

Die Meldung von VisualStudio19 lautet:


The application called an interface that was marshalled for a different thread. (Exception from HRESULT: 0x8001010E (RPC_E_WRONG_THREAD))

Die Frage ist nun: wird dein TaskFactory Abhilfe schaffen? Ich habe schon mehrere "mögliche Lösungen" getestet (wie man noch im Code sieht), keine hat geholfen.

Danke schonmal im Voraus, Domi

Abt Themenstarter:in
16.805 Beiträge seit 2008
vor 2 Jahren

wird dein TaskFactory Abhilfe schaffen?

Weiß ich nich; eher unwahrscheinlich. Aber probiers doch aus..? Was hindert dich? Dein Code halt so, wie man das in WPF bzw. Xamarin, wenn ich das richtig sehe, nicht tun sollte.
Du wirst wahrscheinlich irgendwo ne manuelle Synchronisation vergessen haben, die notwendig ist, bekommst dafür nun die Quittung als Zugriffsverletzung.
Aber ich kenn auch Xamarin nicht mehr; Jahre nicht mehr verwendet.

Mit Bindings ( [Artikel] MVVM und DataBinding) bekommst halt Thread-Synchronität geschenkt, warum nutzt das nicht?
Für Xamarin: Das Model-View-ViewModel-Muster - Xamarin

Auch das ganze manuelle Updaten über einen Timer kannst Du damit prinzipiell sparen; oder zB Reactive Extensions / Reactive UI nutzen, das die Threads automatisch synchronisiert.
Der Thread ist darüber hinaus über 5 Jahre alt. Ich wüsste nicht mal mehr, was mein Code damals getan hat 🙂

4.929 Beiträge seit 2008
vor 2 Jahren

Hallo Dominicano,

du fragst ja auch die UI-Komponente noch im Background-Thread (und nicht im UI-Thread) ab:


if (... && LampenLabel[j].BackgroundColor == Color.Red && ...)

Packe also diese Abfrage innerhalb des Dispatcher-Codes.

Und


LampenLabel[0]

ist sicherlich ein Fehler (im ersten Dispatcher-Code) und du möchtest


LampenLabel[j]

Außerdem vermischt du generell hier Logik und UI-Code, das sollte man vermeiden.
Also befolge Abt's Rat und steige auf MVVM (DataBinding) um - das direkt Arbeiten mit verschiedenen Threads ist, wie du siehst, sehr fehleranfällig.

PS: Auch den Schreibzugriff auf die Variable j führst du in unterschiedlichen Threads aus (die Anweisung j++ führst du doch logisch jedesmal aus, also reicht diese Anweisung einmalig auszuführen - nach if ... else).

D
23 Beiträge seit 2007
vor 2 Jahren

Hi Th69

Ja, LampenLabel[0] war falsch.
Also :


private bool UpdateLabel1()
        {
            if (j < 32 && LampenLabel[j].BackgroundColor == Color.Red && Lampen[j])
                Device.BeginInvokeOnMainThread(async () =>
                {
                    LampenLabel[j].BackgroundColor = Color.Green;
                    //System.Diagnostics.Debug.WriteLine("Label geändert zu grün: " + j);
                    j++;
                });

            else if (j < 32 && LampenLabel[j].BackgroundColor == Color.Green & !Lampen[j])
                Device.BeginInvokeOnMainThread(async () =>
                {
                    LampenLabel[j].BackgroundColor = Color.Red;
                    //System.Diagnostics.Debug.WriteLine("Label geändert zu rot: " + j);
                    j++;
                });
            else
                j++;
            if (j > 31)
            {
                j = 0;
                return false;
            }
            else
                return true;
        }

funktioniert auf Android-Geräten, Windows 8.1 , 10 und Phone reibungslos. IOS kann ich mangels IPhone oder Mac nicht testen. Nur bei Windows IoT auf dem RasPi halt nicht. (Liegt es an Windows? Selbst bei der Serververbindung bekomme ich teilweise SoketExceptions wo nie welche kamen...)

"j" mußte außerhalb deklariert werden da eine for-Schleife nur Exceptions brachte


private bool UpdateLabel4() // funktioniert nicht!
        {
            Device.BeginInvokeOnMainThread(async () => {
            for (int i = 1; i < 32; i++)
                if (LampenLabel[i].BackgroundColor == Color.Red && Lampen[i])
                    LampenLabel[i].BackgroundColor = Color.Green;
                else if (LampenLabel[i].BackgroundColor == Color.Green & !Lampen[i])
                    LampenLabel[i].BackgroundColor = Color.Red; 
            });
            return false;
        }

Binding der BackgroundProperty habe ich heute auch versucht. Laut Debug werden die Farben geändert , nur in der Gui nicht.

Habe evtl. einen leichteren Weg gefunden: (eine schon früher geschriebene) Win-Forms.exe läßt sich auch (mit zwei zusätzlichen Programmen) so auf dem PI ausführen.
Evtl. braucht es ja mal jemand:https://tutorials-raspberrypi.de/raspberry-pi-gui-apps-c-sharp-dot-net/