none
Threading mit multiplen Videos, Slideshows, etc. RRS feed

  • Frage

  • Hi,

    es geht um multiple GUI-Threads, bzw. um die verschiedensten Fehlermeldungen wenn man diese so benutzt wie hier angegeben.

    Ich habe schon mehrere Threads hier gehabt mit dem diesem Thema, hänge aber jedes Mal aufs Neue daran fest.

    Ich habe 8 bis 16 Monitore an einem Rechner angschlossen und muss nun in diesen Fenstern verschiedene Dinge mit meinem Programm darstellen. Einige Animationen, einige Videos, etc.
    Rufe ich aber 10 Fenster im gleichen Thread auf, so spielt nur noch eines der Videos, die anderen hängen sich auf.

    In meiner App.xaml.cs rufe ich die Fenster in einer Schleife auf (meist ein Fenster mehrmals erstellt). Übergebe ihnen dabei den Namen der Konfiguration, also was angezeigt werden soll und die Nummer des Monitors auf dem sie dargestellt werden soll.

    Wie muss ich hier vorgehen, damit die Animationen, Videos, etc. in eigenen Threads laufen und nicht alle auf dem einen GUI-Thread? Ich weiß wie man mehrere GUI-Threads erstellt, aber dabei kommt es immer wieder zu einer großen Menge Fehlern unterschiedlichster Art. Kann man vielleicht die Steuerelemente einzeln threaden?

    Für Interessierte hier noch mein gesamter Code von dem Fenster mit den Videos und Animationen.

    WheelOfFortune.xaml:

    <Window x:Class="ASA_Videowand.WheelOfFortune"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="WheelOfFortune" 
            Name="WheelWindow"
            WindowStyle="None"
            Height="1080" 
            Width="1920"
            Loaded="WheelOfFortune_OnLoaded">
        <Window.Resources>
            <Style x:Key="ImgZIndexStyle" TargetType="{x:Type Image}">
                <Setter Property="Panel.ZIndex" Value="2"/>
                <Style.Triggers>
                    <Trigger Property="Image.Opacity" Value="1">
                        <Setter Property="Panel.ZIndex" Value="3"/>
                    </Trigger>
                </Style.Triggers>
            </Style>
            <Storyboard x:Key="FaderStoryboardHide1Show2">
                <DoubleAnimation Storyboard.TargetName="FirstImage" 
                 Storyboard.TargetProperty="Opacity"
                 To="0" Duration="0:00:01" />
                <DoubleAnimation Storyboard.TargetName="SecondImage" 
                 Storyboard.TargetProperty="Opacity"
                 To="1" Duration="0:00:01" />
            </Storyboard>
            <Storyboard x:Key="FaderStoryboardHide2Show1">
                <DoubleAnimation Storyboard.TargetName="SecondImage" 
                 Storyboard.TargetProperty="Opacity"
                 To="0" Duration="0:00:01" />
                <DoubleAnimation Storyboard.TargetName="FirstImage" 
                 Storyboard.TargetProperty="Opacity"
                 To="1" Duration="0:00:01" />
            </Storyboard>
        </Window.Resources>
        <Grid>
            <Grid Name="SlideShowGrid" ZIndex="0">
                <Image x:Name="FirstImage" Stretch="UniformToFill" Style="{StaticResource ImgZIndexStyle}" Opacity="1" />
                <Image x:Name="SecondImage" Stretch="UniformToFill" Style="{StaticResource ImgZIndexStyle}" Opacity="0"/>
            </Grid>
            <Canvas Name="VideoCanvas" ZIndex="0">
                <Button Name="VideoButton" Click="VideoButton_OnClick">
                    <MediaElement Height="{Binding ElementName=WheelWindow, Path=Height}" 
                                  Width="{Binding ElementName=WheelWindow, Path=Width}"
                                  UnloadedBehavior="Manual"
                                  MediaEnded="MarketingVideoMediaElement_OnMediaEnded"
                                  Name="MarketingVideoMediaElement" />
                </Button>
            </Canvas>
            <Grid Name="WheelGrid" ZIndex="1">
                <Button Name="SpinWheelButton" Click="SpinWheelButton_OnClick">
                    <Grid>
                        <Image Source="pics/gluecksrad_background.jpg" Stretch="UniformToFill" />
                        <Border>
                            <Border.Effect>
                                <DropShadowEffect Direction="330" Opacity="0.7" ShadowDepth="5" Color="Black" BlurRadius="3" RenderingBias="Performance" />
                            </Border.Effect>
                            <Image Name="ArrowImage" Source="pics/pfeil.png" Height="300" RenderTransformOrigin="0.5,0.5" Margin="414,330,1390,420">
                                <Image.RenderTransform>
                                    <RotateTransform />
                                </Image.RenderTransform>
                            </Image>
                        </Border>
                    </Grid>
                </Button>
            </Grid>
        </Grid>
    </Window>

    WheelOfFortune.xaml.cs:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Windows;
    using System.Windows.Forms;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Threading;
    using Microsoft.Win32;
    using Panel = System.Windows.Controls.Panel;
    
    namespace ASA_Videowand
    {
        /// <summary>
        /// Interaktionslogik für WheelOfFortune.xaml
        /// </summary>
        public partial class WheelOfFortune
        {
            private int _listCounter;
            private int _imageCounter;
            private int _countTimer;
            private int _screenNumber;
            private List<string> _fileList;
            private DoubleAnimation _rotateAnimation;
            private readonly int _imageChangeTime;
            private readonly string _myPath;
            private readonly List<int> _targetAngelList = new List<int>();
            private readonly DispatcherTimer _videoTimer = new DispatcherTimer();
            private readonly DispatcherTimer _slideShowTimer = new DispatcherTimer();
    
            public WheelOfFortune(int screenNumber, string configName)
            {
                InitializeComponent();
                BuildList();
                CreateVideoTimer();
                // Die Monitornummer übernehmen für das spätere Verschieben und Maximieren
                _screenNumber = screenNumber;
                // ConfigInfos auslesen
                var regKey = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
                var openSubKey = regKey.OpenSubKey(@"SOFTWARE\ASA\Videowand", false);
                if (openSubKey != null)
                {
                    // Pfad der Anwendung
                    _myPath = openSubKey.GetValue("ProgramPath").ToString();
                    // Zeit die zwischen den Bilderwechseln vergehen soll
                    _imageChangeTime = Convert.ToInt32(openSubKey.GetValue("SlideShowTimer"));
                }
                // Liste mit Werbevideos/-bildern erstellen und abspielen
                GetDataFromHdd(configName);
            }
    
            private void GetDataFromHdd(string configName)
            {
                // Dateien aus den Subverzeichnissen auslesen, welche den übergebenen Config-Namen beinhalten
                foreach (var fileList in from subDirectory in Directory.GetDirectories(_myPath, "*", SearchOption.AllDirectories) 
                                         where subDirectory.Contains(configName) 
                                         select Directory.GetFiles(subDirectory, "*", SearchOption.TopDirectoryOnly).ToList())
                {
                    // Prüfen ob die erste Datei darin ein Bild ist...
                    if (fileList[0].ToUpper().Contains(".JPG") || fileList[0].ToUpper().Contains(".GIF") ||
                        fileList[0].ToUpper().Contains(".JPEG") || fileList[0].ToUpper().Contains(".PNG") ||
                        fileList[0].ToUpper().Contains(".TIFF") || fileList[0].ToUpper().Contains(".BMP"))
                    {
                        _fileList = fileList;
                        // ... dann diese Bilder als SlideShow zeigen
                        BuildSlideShow();
                    }
                    else
                    {
                        // ... oder ein Video, dann dieses abspielen
                        ShowMovie(fileList[0]);
                    }
                }
            }
    
            private void ShowMovie(string fileName)
            {
                // Vorbereitungen für das Video treffen
                CreateVideoStoryboard();
                // Das Video vor das Glücksrad setzen
                Panel.SetZIndex(VideoCanvas, 2);
                // Video abspielen
                MarketingVideoMediaElement.Source = new Uri(fileName);
            }
    
            private void BuildSlideShow()
            {
                // Die Slideshow vor das Glücksrad setzen und dieses unsichtbar machen
                Panel.SetZIndex(SlideShowGrid, 2);
                WheelGrid.Opacity = 0;
                // Das erste Bild und den Timer initialisieren und starten
                FirstImage.Source = new BitmapImage(new Uri(_fileList[0]));
                _slideShowTimer.Interval = new TimeSpan(0, 0, _imageChangeTime);
                _slideShowTimer.Tick += Timer_Tick;
                _slideShowTimer.Start();
            }
    
            private void Timer_Tick(object sender, EventArgs e)
            {
                // Temporäres Image und Storyboard erstellen um damit zu arbeiten, statt mit den Originalen
                System.Windows.Controls.Image newImage;
                Storyboard tempStoryboard;
                // Testen ob das erste Bild sichtbar ist und temporäre Variablen belegen mit den Originalen
                if (FirstImage.Opacity == 1)
                {
                    tempStoryboard = (Storyboard) FindResource("FaderStoryboardHide1Show2");
                    newImage = SecondImage;
                }
                else
                {
                    tempStoryboard = (Storyboard)FindResource("FaderStoryboardHide2Show1");
                    newImage = FirstImage;
                }
                // Testen ob das Ende der Dateiliste erreicht ist
                if (_imageCounter >= _fileList.Count - 1)
                    // Dateiliste wieder auf Anfang setzen
                    _imageCounter = 0;
                else
                    // Nächstes Bild in Angriff nehmen
                    _imageCounter++;
                // Angezeigtes Bild ändern
                newImage.Source = new BitmapImage(new Uri(_fileList[_imageCounter]));
                // Animation starten
                tempStoryboard.Begin();
            }
    
            private void CreateVideoTimer()
            {
                // VideoTimer erstellen mit Sekunden zählen
                _videoTimer.Interval = TimeSpan.FromSeconds(1);
                _videoTimer.Tick += VideoTimerOnTick;
            }
    
            private void VideoTimerOnTick(object sender, EventArgs eventArgs)
            {
                _countTimer++;
                if (_countTimer <= 8) return;
                // Schiebe Video in den Vordergrund
                Panel.SetZIndex(VideoCanvas, 2);
                _videoTimer.Stop();
                _countTimer = 0;
            }
    
            private void BuildList()
            {
                // Liste erstellen mit den Positionen (Gradzahlen 0 bis 359) an denen das Rad zum Stehen kommen kann
                _targetAngelList.Add(11); // 1 Chip
                _targetAngelList.Add(33); // 1 Chip
                _targetAngelList.Add(56); // 1 Chip
                _targetAngelList.Add(78); // 0 Chips
                _targetAngelList.Add(101); // 2 Chips
                _targetAngelList.Add(123); // 0 Chips
                _targetAngelList.Add(146); // 1 Chip
                _targetAngelList.Add(168); // 1 Chip
                _targetAngelList.Add(191); // 1 Chip
                _targetAngelList.Add(214); // 1 Chip
                _targetAngelList.Add(237); // 1 Chip
                _targetAngelList.Add(259); // 1 Chip
                _targetAngelList.Add(281); // 0 Chips
                _targetAngelList.Add(304); // 2 Chips
                _targetAngelList.Add(327); // 0 Chips
                _targetAngelList.Add(349); // 1 Chip
            }
    
            private void SpinWheelButton_OnClick(object sender, RoutedEventArgs e)
            {
                // Timer zurücksetzen, damit das Video nicht sofort erscheint
                _countTimer = 0;
                // Zufallszahl ermitteln und aus der Liste einen Eintrag zufällig auswählen
                var targetAngel = new Random();
                _listCounter = targetAngel.Next(0, _targetAngelList.Count - 1);
                // Ziel der Animation auf 5 Umdrehungen (1800) plus den Endstand des Pfeils 
                _rotateAnimation.To = 1800 + _targetAngelList[_listCounter];
                // Bei Klick auf dem Rad soll der Pfeil sich anfangen zu drehen
                ((Storyboard)Resources["Storyboard"]).Begin();
            }
    
            private void CreateVideoStoryboard()
            {
                // Storyboard erstellen und die Anzahl der Millisekunden einstellen die das Rad braucht um stehen zu bleiben
                var spinningStoryboard = new Storyboard
                {
                    Duration = new Duration(TimeSpan.FromMilliseconds(2500)),
                };
                // Animation erstellen mit den Parametern von wo nach wo es sich drehen, wie lange es bis zum Ziel brauchen und um wie viel es langsamer werden soll
                _rotateAnimation = new DoubleAnimation
                {
                    From = 0,
                    To = 1800 + _targetAngelList[_listCounter],
                    Duration = spinningStoryboard.Duration,
                    DecelerationRatio = 0.9
                };
                // Storyboard mit der Animation und dem Ziel der Animation füttern und es unter this.Resources bekannt machen
                Storyboard.SetTarget(_rotateAnimation, ArrowImage);
                Storyboard.SetTargetProperty(_rotateAnimation, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)"));
                spinningStoryboard.Children.Add(_rotateAnimation);
                Resources.Add("Storyboard", spinningStoryboard);
            }
    
            private void VideoButton_OnClick(object sender, RoutedEventArgs e)
            {
                // Schiebe Video in den Hintergrund
                Panel.SetZIndex(VideoCanvas, 0);
                // Start Timer
                _videoTimer.Start();
            }
    
            private void MarketingVideoMediaElement_OnMediaEnded(object sender, RoutedEventArgs e)
            {
                // Schleifencode, nach dem Ende des Videos wieder von vorn beginnen
                MarketingVideoMediaElement.Position = new TimeSpan(0, 0, 0, 0, 1);
                MarketingVideoMediaElement.Play();
            }
    
            private void WheelOfFortune_OnLoaded(object sender, RoutedEventArgs e)
            {
                ShowOnMonitor();
            }
    
            private void ShowOnMonitor()
            {
                // Screenarray erstellen und alle Monitore dort hineinstecken
                Screen[] 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);
                // Fenster maximieren
                WindowState = WindowState.Maximized;
            }
        }
    }

    Achja, und ja, es sind teilweise Touchscreens, daher die Interaktivitätsoptionen mit Buttons, etc.

    Ich hatte es früher mal so wie in diesem Thread hier beschrieben, aber das führte zu mehreren Problemen beim Aufruf: https://social.msdn.microsoft.com/Forums/windowsapps/de-DE/3567bdcf-55d8-4e06-82e8-ddf5608451bd/wpf-mehrere-webbrowser-controls-in-verschiedenen-threads-darstellen?forum=visualcsharpde

    Irgendjemand ne Lösung für mich?




    Mittwoch, 4. März 2015 11:03

Antworten

  • Hallo,

    Als erstes empfehle ich dir die Fenster einfach alle zu erzeugen und dann mit Show anzuzeigen. Das sollte relativ einfach sein keine Probleme mit den Threads verursachen. Später auch ein bei mir funktionierendes Beispiel dazu.

    Weiterhin empfehle ich dir das durchlaufen aller Bildschirme zu modifizieren. Momentan rufst du über die _xml-Variable die einzelnen Konfigurationen mit Hilfe einer List<string> ab. Das kann leider schnell zu Problemen führen, wenn man mal etwas ändern will. Ich würde daher eine Klasse, wie folgende, dafür einsetzen:

    public class MyScreenConfig
    {
        public MyScreenConfig(Screen scr)
        {
            this.Screen = scr;
        }
        public bool DisplayOnScreen { get; set; }
        public bool IsMarketingWebBrowser { get; set; }
        public Screen Screen { get; private set; }
    }

    Dann könnte das Starten der Anwendung wie folgt ablaufen (bitte beachte meine Kommentare im Code):

    List<Window> windows;
    
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        windows = new List<Window>();
        // Durch alle angeschlossenen Monitore durchgehen
        for (int i = 0; i < Screen.AllScreens.Length; ++i)
        {
            var config = _xml.GetScreenConfigs(i);//Methode liefert ein MyScreenConfig
    
            // Abfrage ob der Monitor angezeigt werden soll
            if (config.DisplayOnScreen)
            {
                Window wnd;
    
                // Abfrage ob es ein Marketing-WebBrowser ist
                if (config.IsMarketingWebBrowser) //jeweiliges Initialisieren
                    wnd = new WheelOfFortune(i, config);
                else
                    wnd = new Shelf(i, config);
    
                //Position und Größe kann auch gleich hier gesetzt werden
                wnd.Left = config.Screen.Bounds.Left;
                wnd.Top = config.Screen.Bounds.Top;
                wnd.Height = config.Screen.Bounds.Height;
                wnd.Width = config.Screen.Bounds.Width;
    
                wnd.Closed += wnd_Closed;
                wnd.Loaded += (s2, e2) => (s2 as Window).WindowState = WindowState.Maximized;//Fenster maximieren, nachdem es angezeigt wurde
                wnd.Show();//Fenster anzeigen
                windows.Add(wnd);//Fenster zur Fensterliste hzinzufügen
    
            }
        }
    }
    
    
    void wnd_Closed(object sender, EventArgs e)
    {
        //Ein Fenster wurde geschlossen > alle Fenster schließen
        foreach (var wnd in windows)
        {
            if (wnd != sender)
            {
                //Es handelt sich nicht um das geschlossene Fenster
                wnd.Closed -= wnd_Closed;//Event deabonnieren
                wnd.Close();//Fenster schließen
            }
        }
    }

    Durch den von mir eingebauten Mechanismus zum Schließen sollte es auch kein Problem mehr mit dem Geisterprozess geben.

    Auch die von dir geposteten Fehler sollten dadurch vermieden werden.

    Wenn du noch Fragen hast, beantworte ich dir diese gern.


    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    • Als Antwort markiert Marcel Gpunkt Montag, 23. März 2015 07:36
    Sonntag, 8. März 2015 16:44
    Moderator

Alle Antworten

  • Hallo,
    zunächst habe ich eine Frage zur Anordnung der Monitore: Sind diese immer in einem Rechteckigen Muster angeordnet? Wenn ja, dann könntest du auch nur ein Fenster verwenden, welches entsprechend über alle Monitore gestreckt wird. Das sollte nicht schlechter laufen als viele mit einander synchronisierte Fenster. Bei mehreren Fenstern hast du dann entsprechend das Problem der Synchronisation.

    Das Threading ist kein einfaches Thema - ich kann die Probleme daher gut nachvollziehen. Wichtig zu wissen ist, dass jedes benutzbare Fenster seinen eigenen Thread braucht. Das kann einfach erreicht werden indem man dieses per Show-Methode anzeigt. Windows legt dann automatisch einen neuen GUI-Thread an und führt das Fenster in diesem aus. Wenn du Fenster dagegen mit ShowDialog aufrufst, wird zwar IMHO auch ein neuer Thread angelegt - das zu Grunde liegende Fenster wird jedoch blockiert.
    Steuerelemente können keinen eigenen Thread bekommen, da sämtliche Zugriffe sowieso wieder mit dem Fenster-GUI-Thread synchronisiert werden müssen (per Dispatcher).

    Etwas anders läuft es nochmal mit WPF Animationen. Diese werden von der Grafikkarte verarbeitet, weswegen sie aus dem normalen Threadingkonzept heraus fallen und asynchron laufen können.

    Um dir besser weiter helfen zu können müssten wir jedoch wissen wo welcher Fehler auftritt (inkl. Fehlermeldung).
    Wichtig wäre wohl auch die Schleife mit der du die Fenster erzeugst.


    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Mittwoch, 4. März 2015 15:30
    Moderator
  • Das Programm muss auf mehreren PCs laufen, einige haben 8 Monitore, einige 10 und einige 16. Die sind aber immer in 2 Reihen angeordnet. Wobei diese Anordnung sich auch ändern kann.

    Ich habe mal einige Fehler hier zusammengefasst:

    http://pastebin.com/hCgyHwik

    Das waren aber noch nicht alle. Es kommen immer wieder neue (ohne die Syntax zu ändern) bei jedem Neustart. Ich gehe einfach davon aus, dass sich da irgendwelche Threads gegenseitig den Speicher blockieren oder so. Wäre es C++ und nicht C#, würde ich auf nen Pointerfehler tippen.

    Also das meiste sind Animationen (Glücksrad und SlideShows), die Movies sind 1,5 sekunden lange asf-Dateien die in einer Endlosschleife immer wiederholt werden und dann ist da noch ein von mir selbst geschriebenes Programm dass einfach nur ein paar Bilder anzeigt und auf Clicks weitere Bilder anzeigt, etc.

    Die Schleife mit der ich das ganze starte ist diese hier in der 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
    {
        public partial class App
        {
            private readonly XmlFunctions _xml = new XmlFunctions();
    
            private void Application_Startup(object sender, StartupEventArgs e)
            {
                // Check for CommandLineArguments
                string[] commandLineArgs = Environment.GetCommandLineArgs();
                if (commandLineArgs.Length > 1)
                {
                    switch (commandLineArgs[1])
                    {
                        case "admin":
                        {
                            var adminWindow = new AdminConsole();
                            adminWindow.Show();
                            break;
                        }
                        case "sync":
                        {
                            var syncWindow = new SyncScreen(false);
                            syncWindow.Show();
                            break;
                        }
                        case "autosync":
                        {
                            var syncWindow = new SyncScreen(true);
                            syncWindow.Show();
                            break;
                        }
                        default:
                            MessageBox.Show("Falscher Kommandozeilenparameter!");
                            break;
                    }
                }
                else
                {
                    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")
                            {
                                int i1 = i;
                                //List<string> strings = myStrings;
                                //var wheelWindow = new WheelOfFortune(i1, myStrings[0]);
                                //wheelWindow.Show();
                                List<string> strings = myStrings;
                                var newWindowThread = new Thread(() =>
                                {
                                    SynchronizationContext.SetSynchronizationContext(
                                        new DispatcherSynchronizationContext(Dispatcher.CurrentDispatcher));
                                    var wheelWindow = new WheelOfFortune(i1, strings[0]);
                                    wheelWindow.Closed +=
                                        (sender2, e2) =>
                                            Dispatcher.CurrentDispatcher.BeginInvokeShutdown(
                                                DispatcherPriority.Background);
                                    wheelWindow.Show();
                                    Dispatcher.Run();
                                });
                                newWindowThread.SetApartmentState(ApartmentState.STA);
                                newWindowThread.Start();
                            }
                            else
                            {
                                // Anzeige des Monitors durch Aufruf der Klasse Shelf
                                var myShelf = new Shelf(i, myStrings[0]);
                                myShelf.Show();
                            }
                        }
                        // Monitorzähler erhöhen
                        Thread.Sleep(1500);
                        i++;
                    }
                }
            }
        }
    }
    Das war übrigens nur die letzte Testvariante von mir, vorher hatte ich diese hier von dir benutzt:
                                List<string> strings = myStrings;
                                var newWindowThread = new Thread(() =>
                                {
                                    var wheelWindow = new WheelOfFortune(i1, strings[0]);
                                    wheelWindow.Show();
                                    wheelWindow.Closed += (sender2, e2) => wheelWindow.Dispatcher.InvokeShutdown();
                                    Dispatcher.Run();
                                });
                                newWindowThread.SetApartmentState(ApartmentState.STA);
                                newWindowThread.Start();
    Übrigens habe ich noch ein kleineres Problem, welches damit zu tun hat, dass ich das ganze nicht aus einem Window heraus mache, sondern aus der App.xaml.cs. Wenn ich nämlich den IsBackgroundTask oder wie das hiess auf true setze, dann beendet sich das Programm nicht beim Schließen des Fensters.
    Mittwoch, 4. März 2015 15:52
  • Hallo,

    Als erstes empfehle ich dir die Fenster einfach alle zu erzeugen und dann mit Show anzuzeigen. Das sollte relativ einfach sein keine Probleme mit den Threads verursachen. Später auch ein bei mir funktionierendes Beispiel dazu.

    Weiterhin empfehle ich dir das durchlaufen aller Bildschirme zu modifizieren. Momentan rufst du über die _xml-Variable die einzelnen Konfigurationen mit Hilfe einer List<string> ab. Das kann leider schnell zu Problemen führen, wenn man mal etwas ändern will. Ich würde daher eine Klasse, wie folgende, dafür einsetzen:

    public class MyScreenConfig
    {
        public MyScreenConfig(Screen scr)
        {
            this.Screen = scr;
        }
        public bool DisplayOnScreen { get; set; }
        public bool IsMarketingWebBrowser { get; set; }
        public Screen Screen { get; private set; }
    }

    Dann könnte das Starten der Anwendung wie folgt ablaufen (bitte beachte meine Kommentare im Code):

    List<Window> windows;
    
    private void Application_Startup(object sender, StartupEventArgs e)
    {
        windows = new List<Window>();
        // Durch alle angeschlossenen Monitore durchgehen
        for (int i = 0; i < Screen.AllScreens.Length; ++i)
        {
            var config = _xml.GetScreenConfigs(i);//Methode liefert ein MyScreenConfig
    
            // Abfrage ob der Monitor angezeigt werden soll
            if (config.DisplayOnScreen)
            {
                Window wnd;
    
                // Abfrage ob es ein Marketing-WebBrowser ist
                if (config.IsMarketingWebBrowser) //jeweiliges Initialisieren
                    wnd = new WheelOfFortune(i, config);
                else
                    wnd = new Shelf(i, config);
    
                //Position und Größe kann auch gleich hier gesetzt werden
                wnd.Left = config.Screen.Bounds.Left;
                wnd.Top = config.Screen.Bounds.Top;
                wnd.Height = config.Screen.Bounds.Height;
                wnd.Width = config.Screen.Bounds.Width;
    
                wnd.Closed += wnd_Closed;
                wnd.Loaded += (s2, e2) => (s2 as Window).WindowState = WindowState.Maximized;//Fenster maximieren, nachdem es angezeigt wurde
                wnd.Show();//Fenster anzeigen
                windows.Add(wnd);//Fenster zur Fensterliste hzinzufügen
    
            }
        }
    }
    
    
    void wnd_Closed(object sender, EventArgs e)
    {
        //Ein Fenster wurde geschlossen > alle Fenster schließen
        foreach (var wnd in windows)
        {
            if (wnd != sender)
            {
                //Es handelt sich nicht um das geschlossene Fenster
                wnd.Closed -= wnd_Closed;//Event deabonnieren
                wnd.Close();//Fenster schließen
            }
        }
    }

    Durch den von mir eingebauten Mechanismus zum Schließen sollte es auch kein Problem mehr mit dem Geisterprozess geben.

    Auch die von dir geposteten Fehler sollten dadurch vermieden werden.

    Wenn du noch Fragen hast, beantworte ich dir diese gern.


    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    • Als Antwort markiert Marcel Gpunkt Montag, 23. März 2015 07:36
    Sonntag, 8. März 2015 16:44
    Moderator
  • Ok, habe ich jetzt mal getan. Es startet wesentlich schneller und flüssiger und wenn man ein Fenster schließt schließt sich auch die App :)

    ABER... leider funktioniert das Ganze noch nicht so wie es soll. Ich habe es zum Test mal auf einem der Rechner mit 10 Monitoren laufen lassen. 3 Videos in Endlosschleife, 5 Slideshows (ebenfalls endlos) und 2 mal meine App. Die Videos liefen 2 mal ruckelfrei, einmal ruckelig für ca. 20 Sekunden (also bis die Slideshows das erste Mal wechseln), dann wurde alles schwarz und der Rechner zeigte nichts mehr an. Der Prozess lief mit einer guten Auslastung (zwischen 15% und 25%) im Hintergrund weiter. Ich konnte in der Zwischenzeit auch nichts anderes tun, da alle Fenster im Hintergrund nicht mehr anklickbar wurden nach Fokusverlust, die Videos waren aber nicht zu sehen...Nur die RAM-Auslastung wurde immer größer (anfangs 250 MB, später bis zu 500MB). Ich habe es noch ein 2tes mal getestet zum Vergleich und dort stoppten die Videos schon nach wenigen Sekunden und die Slideshows wechselten nicht einmal. Die Button-Funktion war auch nicht mehr aktiv (sprich, die App hat nicht mehr auf Clicks reagiert).



    Montag, 9. März 2015 08:00
  • Ok, ich habs jetzt noch ein drittes Mal mit einem ähnlichen Ergebnis laufen lassen. Die Fenster bauen sich zwar erstmal fehlerfrei und schnell auf, aber ruckeln dann und stürzen ab.

    Dann habe ich das Ganze, so wie es jetzt ist, auf Threading umgebaut und die selben Probleme wie vorher dabei bekommen.

    Dann habe ich die App sogar einmal so umgebaut, dass sie sich selbst 16 mal startet und jeweils einen Kommandozeilenparameter übergibt, aber auch das bringt nicht den erhofften Effekt. Es ist dann zwar schneller und flüssiger als mit einem Thread alles zu starten, aber beim Start verschluckt er sich und es werden von 16 Apps nur ca. 4 bis 6 gestartet. Und es ruckelt dann trotzdem alles leicht.

    Noch jemand eine Idee?

    Dienstag, 10. März 2015 13:59
  • Hallo Marcel,

    ich habe mir heute morgen nochmal ein paar Gedanken  zu deinem Problem gemacht.

    Das von dir beschriebene Verhalten, mit dem immer mehr ruckelnden Videos und der Hohen Speicherauslastung deuten darauf hin, dass irgend eine Ressource nicht korrekt wieder frei gegeben wird.

    Ich kann deinen Code leider nicht wirklich testen und sehe auch so nichts, was so ein großes Speicherleck verursachen könnte. Ich kann dir daher leider nur empfehlen zu gucken was die Leistung jedes mal verschlechtert und ggf. zu überprüfen ob der Typ IDisposable o.ä. implementiert.


    Tom Lambert - .NET (C#) MVP
    Wozu Antworten markieren und für Beiträge abstimmen? Klicke hier.
    Nützliche Links: .NET Quellcode | C# ↔ VB.NET Konverter | Account bestätigen (Verify Your Account)
    Ich: Webseite | Code Beispiele | Facebook | Twitter | Snippets

    Dienstag, 10. März 2015 14:41
    Moderator
  • Hi Tom,

    schonmal danke für deine Mühe, aber ich komme einfach nicht dahinter was es sein könnte. Das Video ist gerade mal 1529 KB groß, auch wenn es in einer Schleife läuft, sollte das nicht viel fressen. Die Bilder sind im Schnitt alle 2 MB groß. Aber es werden nur 2 Images gleichzeitig dargestellt, nicht animiert und nur alle 20 Sekunden wird die Source neu belegt, nichtmal neue Objekte erzeugt oder Ähnliches.

    Es gibt einige größere Dateien, wie eine über 200 MB große DBF-Datei, die aber gar nicht geladen wird zu dem Zeitpunkt. (Einfach nur der Sicherheit halber, habe ich diese gelöscht und das Projekt ruckelt dann trotzdem.)

    Wenn ich die Startschleife gegen eine "for(i=0;i<16;i++)" austausche und das Projekt hier auf meinem Entwicklungsrechner ausführe, dann ruckelt es ebenfalls und das trotz recht guter NVIDIA-GraKa.

    Hast Du noch irgendeine Idee was ich noch versuchen könnte?

    Mittwoch, 11. März 2015 08:09
  • Ok, ich hab mal ein Analysetool drüber gejagt und folgendes festgestellt:

    100% der Auslastung entsteht in dem Moment wo es kritisch wird von der "wmvdecod.dll". Es scheint also tatsächlich dieses kleine 1,5 MB große, 1,5 Sekunden lange Video zu sein, dass diesen Fehler auslöst.

    Jemand ne Idee wie ich das verbessern kann? Es war mal eine FlashAnimation und wurde von da in ein fast 500 MB großes QuickTimeMovie exportiert und von da von mir in ein 1,5 MB großes .asf-file (WMV) umgewandelt. Von dort wird es dann per <MediaElement/> von mir abgespielt und immer wieder auf die Startzeit 0,1 Sekunde gesetzt wenn das Ende erreicht ist.


    Edit: Ich hab noch eine Analyse gestartet und jetzt bekomme ich auch eine sehr hohe Auslastung aus der Datei "msvcrt.dll" diese ruft dann zu 74,3% die "wmvdecod.dll" auf. Also wahrscheinlich das Video. Oder sehe ich das falsch?
    Mittwoch, 11. März 2015 10:17