Announcement

Collapse
No announcement yet.

[WPF] Aus Task.Run() Zugriff auf die UI

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • [WPF] Aus Task.Run() Zugriff auf die UI

    Ein herzliches Moin in die Runde :-)

    Ich stehe gerade vor einem etwas kleineren Problem, nehme ich mal an.
    Aber ich fange vorne an...

    Ich habe ein kleines Tool, mit dem ich Services unseres ERP auf den ApplicationServern stoppen, starten und neu starten kann.
    Wir haben aktuell 4 ApplicationServer, weshalb das schon mal eine kleine Erleichterung ist.
    Ich habe das über Tasks gelöst, die Methode zum Starten der Services sieht aktuell so aus:
    Code:
    private async Task StartService()
    {
        foreach (var server in CommonDataModel.ApplicationServers)
        {
            lbxStatus.Items.Add(string.Empty);
            CommonDataModel.ApplicationServersControls[server.Key].Background = Brushes.Orange;
    
            var ServicesList = server.Value ? CommonDataModel.ExtendedServices : CommonDataModel.DefaultServices;
    
            foreach (var service in ServicesList.Reverse())
            {
                lbxStatus.Items.Add(GetListBoxItem($"starting {service.Key} on {server.Key}", Brushes.Orange));
    
                await Task.Run(() =>
                {
                    try
                    {
                        using (ServiceController sc = new ServiceController(service.Key, server.Key))
                        {
                            if (!sc.Status.Equals(ServiceControllerStatus.StartPending) || !sc.Status.Equals(ServiceControllerStatus.Running))
                                sc.Start();
    
                            sc.WaitForStatus(ServiceControllerStatus.Running);
                        }
                    }
                    catch (Exception ex)
                    {
                        lbxStatus.Items.Add(ex.Message);
                    }
                });
    
                lbxStatus.Items.Add(GetListBoxItem($"{service.Key} on {server.Key} is running", Brushes.Green));
            }
    
            CommonDataModel.ApplicationServersControls[server.Key].Background = Brushes.LightGreen;
        }
    }
    Das Problem taucht im Catch-Block in dem Task auf:
    Code:
    await Task.Run(() =>
    {
        try
        {
            using (ServiceController sc = new ServiceController(service.Key, server.Key))
            {
                if (!sc.Status.Equals(ServiceControllerStatus.StartPending) || !sc.Status.Equals(ServiceControllerStatus.Running))
                    sc.Start();
    
                sc.WaitForStatus(ServiceControllerStatus.Running);
            }
        }
        catch (Exception ex)
        {
            lbxStatus.Items.Add(ex.Message);
        }
    });
    Ich kann logischer Weise nicht auf die UI Controls zugreifen, geschweige denn neue Child-Elemente zufügen, was hier im Grunde ja passiert.
    Das Problem ist wie die meisten schon erkannt haben, dass der Task nicht im STAThread läuft.

    Jetzt habe ich im Netz haufenweise Lösungen gefunden, die sich aber alle auf Threads beziehen.
    Ich hatte das anfangs auch mal mit Thread umgesetzt, kam aber nie genau auf das Verhalten, was ich jetzt mit Task erreicht habe. Daher würde ich das ungerne wieder auf Thread umbauen müssen.

    Meine Frage
    Kann ich aus meinem Task heraus irgendwie die UI triggern?

    Danke für Meinungen und Hilfe!

    PHP rocks!
    Eine Initiative der PHP Community

  • #2
    Um das Technik unabhängig zu halten also das es egal ist ob Threads/Tasks sind und egal ob die UI WPF/Winforms/Web ist würde ich einen Vermittler dazwischen empfehlen. Als jemand dem die Threads/Tasks die Fehler geben und wo sich die UI die Fehler abholen können so das die beiden Seiten nicht mehr direkt miteinander sprechen.
    Im einfachsten Fall wäre das ein Queue.(in .Net eine ConcurrentQueue<T>).

    Comment


    • #3
      Super, danke. Das schaue ich mir nachher mal an, muss gerade an einem anderen Projekt was machen.
      PHP rocks!
      Eine Initiative der PHP Community

      Comment


      • #4
        Es war tatsächlich einfacher, wie gedacht. Mit ConcurrentQueue bin ich zwar gescheitert, muss ich zugeben, aber es funktioniert wunderbar, über den Dispatcher:
        Code:
        await Task.Run(() =>
        {
            try
            {
                using (ServiceController sc = new ServiceController(service.Key, server.Key))
                {
                    if (!sc.Status.Equals(ServiceControllerStatus.StartPending) || !sc.Status.Equals(ServiceControllerStatus.Running))
                        sc.Start();
        
                    sc.WaitForStatus(ServiceControllerStatus.Running);
                }
            }
            catch (Exception ex)
            {
                lbxStatus.Dispatcher.Invoke(() =>
                {
                    lbxStatus.Items.Add(new ListBoxItem() { Content = ex.Message, Background = Brushes.Red, Foreground = Brushes.White });
                });
            }
        });
        Ich schaue mir gerade die ganzen Videos von AngelSix zu dem Thema an, falls da noch jemand anders mal Fragen hat, hier habe ich die aktuelle Lösung her: C# Threads, Tasks, Multi-threading & UI Cross-threading (AngelSix)

        Ralf Jansen: Wie würde das mit der ConcurrentQueue aussehen und wäre das besser?

        Danke und Gruß
        Arne
        PHP rocks!
        Eine Initiative der PHP Community

        Comment


        • #5
          Besser liegt im Auge des Betrachters. Es war die Empfehlung die immer funktioniert. Da gibts auch nix zu erklären und zu zeigen. Du hättest in den Tasks/Threads halt die Fehlermeldungen enqueued. Und der UI Thread hätte da regelmäßig reingeguckt und anstehende Fehlermeldungen dequeued. Vorteil ist die vollständige Trennung der Threads/Tasks von der UI. Jetzt hast du eine WPF spezifische Lösung die in deinen Threads stattfindet. In einer umfangreichen Architektur könnte man das für ein Problem halten. Wenn das hier aber eh irgendein kleines Tool ist wo du zum Beispiel den Task.Run eh schon in der Window Klasse einer WPF Anwendung ist das mit der sauberen Trennung eh eher müßig und vorher schon durchbrochen.

          Comment


          • #6
            Du hättest in den Tasks/Threads halt die Fehlermeldungen enqueued. Und der UI Thread hätte da regelmäßig reingeguckt und anstehende Fehlermeldungen dequeued.
            Ja genua, so hatte ich das auch verstanden. Mein Problem war die automatische Aktualisierung der ListBoxItems. Hinterlege ich in der Queue eine Collection von ListBoxItems, die an die ListBox gebunden ist, erhielt ich den selben Fehler. Wenn ich nur die Texte übergebe, ist das kein Problem, müsste dann aber über einen Timer die ListBoxItems immer wieder aktualisieren, zumindest habe ich es nicht anders hinbekommen.
            Das kam mir relativ overloaded vor. Ich hatte wohl die INotifyPropertyChanged Interface genutzt, aber wie gesagt, arbeite ich in der Queue mit einer Collection aus UI Elementen, hatte ich den selben Fehler.
            Sicher werde ich da was falsch gemacht haben, aber im Grunde habe ich ja auch keine allgemeine Lösung gesucht, sondern eine speziell bei Verwendung mit Task.
            Ich denke, dass ich dann eine ganz passable Variante verwende.

            Danke für die Tipps!
            Gruß Arne

            PHP rocks!
            Eine Initiative der PHP Community

            Comment

            Working...
            X