none
WPF - Mehrere WebBrowser controls in verschiedenen Threads darstellen RRS feed

  • Frage

  • Hi,

    ich habe folgendes Problem:

    Ich habe einen Server an dem 10 Monitore angeschlossen sind. Auf 5 davon werden WebBrowser Controls eingebunden. Auf 5 weiteren selbst geschriebene Fenster, aber alles in einer App. Von den 5 WebBrowsern sind 3 mit FlashAnimationen gefüllt. (Diese kann ich leider nicht umgehen und muss damit arbeiten).

    Starte ich die App ist noch alles OK. Starte ich die WebBrowser mit normalem HTML-Inhalt ist auch noch alles OK. Starte ich aber nun die FlashAnimationen wird alles schlimmer. Nach dem ersten Browser ruckelt es minimal, nach dem Zweiten ruckelt es schon spürbar und spätestens beim dritten Fenster wird die App unbenutzbar mit Wartezeiten von bis zu 10 Sekunden pro Klick. RAM-Auslastung der App springt dann von ca. 200 MB auf über 1 GB und die CPU-Auslastung teilweise auf 80% hoch.

    Ich habe den Hinweis bekommen es mit MultiThreading auszuprobieren. Leider stoße ich hier immer wieder auf Probleme. Meine Startdatei sieht dafür so aus (App.xaml.cs):

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Windows;
    using System.Windows.Forms;
    using System.Windows.Threading;
    using MessageBox = System.Windows.MessageBox;
    
    namespace ASA_Videowand
    {
        /// <summary>
        ///     Interaktionslogik für "App.xaml"
        /// </summary>
        public partial class App
        {
            private readonly XmlFunctions _xml = new XmlFunctions();
            private int j = 0;
            private string configName = "";
    
            private void Application_Startup(object sender, StartupEventArgs e)
            {
                // Check for CommandLineArguments
                string[] commandLineArgs = Environment.GetCommandLineArgs();
                int i = 0; // Monitorzähler
                // Durch alle angeschlossenen Monitore durchgehen
                foreach (List<string> myStrings in Screen.AllScreens.Select(myScreen => _xml.GetScreenConfigs(i)))
                {
                    // Abfrage ob der Monitor angezeigt werden soll
                    if (myStrings[1] == "true")
                    {
                        // Abfrage ob es ein Marketing-WebBrowser ist
                        if (myStrings[2] == "true")
                        {
                            j = i;
                            configName = myStrings[0];
                            NewWindowHandler();
                        }
                        else
                        {
                            // Anzeige des Monitors durch Aufruf der Klasse Shelf
                            var myShelf = new Shelf(i, myStrings[0]);
                            myShelf.Show();
                        }
                    }
                    // Monitorzähler erhöhen
                    i++;
                }
            }
        }
    
            private void NewWindowHandler()
            {
                Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
                newWindowThread.SetApartmentState(ApartmentState.STA);
                newWindowThread.IsBackground = true;
                newWindowThread.Start();
            }
    
            private void ThreadStartingPoint()
            {
                // Anzeige des Werbebildschirms durch Aufruf der Klasse Marketing
                Marketing myMarketing = new Marketing(j, configName);
                myMarketing.Show();
                Dispatcher.Run();
            }
        }
    }

    Aber folgende Probleme tauchen dabei auf:

    Ich kann das Fenster nicht mehr maximieren. Es stellt sich kleiner dar als es sein sollte und lässt sich nicht mehr auf verschiedenen Fenstern positionieren. Außerdem kann ich die Variablen nicht mehr an das neue Fenster übergeben, da der ThreadStartingPoint() erst nach der Schleife aufgerufen wird und damit steht nur noch der letzte Inhalt in der Variable.

    Hier der Inhalt der Marketing.xaml.cs (wegen der Monitorpositionierung und Maximierung, etc.):

    using System;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Windows;
    using System.Windows.Forms;
    using System.Windows.Interop;
    using Application = System.Windows.Application;
    
    //using System.Windows.Shapes;
    
    namespace ASA_Videowand
    {
        /// <summary>
        ///     Interaktionslogik für Marketing.xaml
        /// </summary>
        public partial class Marketing
        {
            // Hilfsvariable zur Speicherung der Monitornummer für spätere Verwendung
            private readonly int _screenNumber;
            // Xml-Funktionen einbinden
            private readonly XmlFunctions _xml = new XmlFunctions();
            // Für die Monitorsteuerung ist folgende DLL notwendig
            [DllImport("user32.dll")]
            // Aus dieser DLL benötigen wir diese Funktion
            private static extern IntPtr GetActiveWindow();
    
            public Marketing(int screenNumber, string myLink)
            {
                // Marketingstring auslesen und link aus der Config suchen
                var configList = _xml.GetGeneralConfig();
                switch (myLink.Substring(10))
                {
                    case "1":
                        myLink = configList[5];
                        break;
                    case "2":
                        myLink = configList[6];
                        break;
                    default:
                        myLink = configList[7];
                        break;
                }
                // Fensterkomponenten aufbauen
                InitializeComponent();
                // Fensterzähler für spätere Verwendung festhalten
                _screenNumber = screenNumber;
                // Source für das Marketingfenster festlegen
                MarketingBrowser.Source = myLink.Length != 0 ? new Uri(myLink) : new Uri("http://www.google.de");
            }
    
            private void MarketingLoaded(object sender, RoutedEventArgs e)
            {
                // Auswahl des Monitors vornehmen
                ShowOnMonitor(_screenNumber);
            }
    
            private void ShowOnMonitor(int screenNumber)
            {
                // Screenarray erstellen und alle Monitore dort hineinstecken
                var screenArray = Screen.AllScreens;
                // Sollte die gewählte ScreenNummer höher sein als das Array, so wird diese auf 0 gesetzt um Fehler zu vermeiden
                if (screenNumber >= screenArray.Length)
                {
                    screenNumber = 0;
                }
                // Fenster auf den korrekten Monitor platzieren
                Left = Convert.ToInt32(screenArray[screenNumber].Bounds.Left);
                Top = Convert.ToInt32(screenArray[screenNumber].Bounds.Top);
                // Aktives Fenster auswählen
                var active = GetActiveWindow();
                var singleOrDefault = Application.Current.Windows.OfType<Window>()
                    .SingleOrDefault(window => new WindowInteropHelper(window).Handle == active);
                if (singleOrDefault == null) return;
                // Titel des aktiven Fensters auf "Monitor" plus die Zahl zuweisen
                singleOrDefault.Title = "Monitor" + screenNumber;
                // Namen des aktiven Fensters auf "Monitor" plus die Zahl zuweisen
                singleOrDefault.Name = "Monitor" + screenNumber;
                // Momentanes Fenster maximieren
                singleOrDefault.WindowState = WindowState.Maximized;
    } } }

    Jemand eine Idee wie ich das bewerkstelligen kann? Oder ist die MultiThreadingIdee eine schnapsige und es wäre besser ein anderes control zu verwenden?



    Mittwoch, 3. Dezember 2014 10:19

Antworten

  • Ok, das Verschieben auf einen anderen Monitor schlug wegen eines falschen Aufrufs fehl, so sollte es aussehen, dann klappt auch mit dem Nachbarn (oder so ähnlich):

    int i1 = i;
    List<string> strings = myStrings;
    Thread newWindowThread = new Thread(() =>
    {
      Marketing myMarketing = new Marketing(i1, strings[0]);
      myMarketing.Show();
      Dispatcher.Run();
    });
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
    

    Und in der Zieldatei ist folgendes ganz wichtig:

        public partial class Marketing
        {
            static readonly object _locker = new object();
    ...
            public Marketing(int screenNumber, string myLink)
            {
                lock (_locker)
                {
                    InitializeComponent();
                }
    ...
            }
    
            private void ShowOnMonitor(int screenNumber)
            {
                var screenArray = Screen.AllScreens;
                if (screenNumber >= screenArray.Length)
                {
                    screenNumber = 0;
                }
                Left = Convert.ToInt32(screenArray[screenNumber].Bounds.Left);
                Top = Convert.ToInt32(screenArray[screenNumber].Bounds.Top);
                WindowState = WindowState.Maximized;
            }
        }
    

    Ich glaube das mit dem suchen des aktiven Fensters, etc. kam noch aus der Zeit, als das Projekt noch in WindowsForms gebaut war. Auf jeden Fall funktioniert das setzen des WindowState auch ganz simpel so.

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 4. Dezember 2014 15:11
    Donnerstag, 4. Dezember 2014 15:11

Alle Antworten

  • Ich hab das mal etwas umgebaut:

    // Abfrage ob es ein Marketing-WebBrowser ist
       if (myStrings[2] == "true")
       {
          Thread newWindowThread = new Thread(() =>
          {
             Marketing myMarketing = new Marketing(i, myStrings[0]);
             myMarketing.Show();
             Dispatcher.Run();
          });
          newWindowThread.SetApartmentState(ApartmentState.STA);
          newWindowThread.IsBackground = true;
          newWindowThread.Start();
       }

    Damit kann ich zumindest die falsche Datenübergabe umgehen. Aber die Fenster sind dann trotzdem nicht platzierbar und es klappt auch nicht mehr mit dem Maximieren.

    [edit]

    Ok... bin noch einen Schritt weiter. Ich bekomme folgenden Fehler bei folgender Zeile im Marketing.xaml.cs:

    var singleOrDefault = Application.Current.Windows.OfType<Window>()
                    .SingleOrDefault(window => new WindowInteropHelper(window).Handle == active);

    Application.Current.Windows "Application.Current.Windows" hat eine Ausnahme vom Typ "System.InvalidOperationException" verursacht.

    base = {"Der aufrufende Thread kann nicht auf dieses Objekt zugreifen, da sich das Objekt im Besitz eines anderen Threads befindet."}

    Da bricht er dann ab und daher maximiert er das Fenster auch nicht mehr.

    Wie umgehe ich das? Kann ich das Fenster auch noch anderweitig auf einem anderen Monitor platzieren und maximieren?

    Donnerstag, 4. Dezember 2014 07:19
  • Ok, das Verschieben auf einen anderen Monitor schlug wegen eines falschen Aufrufs fehl, so sollte es aussehen, dann klappt auch mit dem Nachbarn (oder so ähnlich):

    int i1 = i;
    List<string> strings = myStrings;
    Thread newWindowThread = new Thread(() =>
    {
      Marketing myMarketing = new Marketing(i1, strings[0]);
      myMarketing.Show();
      Dispatcher.Run();
    });
    newWindowThread.SetApartmentState(ApartmentState.STA);
    newWindowThread.IsBackground = true;
    newWindowThread.Start();
    

    Und in der Zieldatei ist folgendes ganz wichtig:

        public partial class Marketing
        {
            static readonly object _locker = new object();
    ...
            public Marketing(int screenNumber, string myLink)
            {
                lock (_locker)
                {
                    InitializeComponent();
                }
    ...
            }
    
            private void ShowOnMonitor(int screenNumber)
            {
                var screenArray = Screen.AllScreens;
                if (screenNumber >= screenArray.Length)
                {
                    screenNumber = 0;
                }
                Left = Convert.ToInt32(screenArray[screenNumber].Bounds.Left);
                Top = Convert.ToInt32(screenArray[screenNumber].Bounds.Top);
                WindowState = WindowState.Maximized;
            }
        }
    

    Ich glaube das mit dem suchen des aktiven Fensters, etc. kam noch aus der Zeit, als das Projekt noch in WindowsForms gebaut war. Auf jeden Fall funktioniert das setzen des WindowState auch ganz simpel so.

    • Als Antwort markiert Marcel Gpunkt Donnerstag, 4. Dezember 2014 15:11
    Donnerstag, 4. Dezember 2014 15:11