JPG asynchron in BitmapImage laden

  • WPF
  • .NET (FX) 4.5–4.8

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    JPG asynchron in BitmapImage laden

    Hallo Leute.
    Das Programm soll Fotos schneller und daher im Hintergrund laden. Man kann die Fotos per Button weiterschalten. Es existiert eine Collection mit breits geladenen BitmapImages, die dann einem Property zugewiesen werden. Das funktioniert auch alles. Jetzt der Versuch das nächste Bild für die als Puffer dienenden Collection asynchron zu laden. Leider scheitere ich seit Tagen daran, weil die Daten zw. den Threads nicht ausgetauscht werden.

    Dieser Teil wird beim Weiterschalten in einer asynchronen Sub ausgeführt.

    VB.NET-Quellcode

    1. Dim BitmapImage_E = Await Task.Run(Function() Laden(Index))
    2. Coll.Add(New Foto_Speicher_Klasse With {.Foto = BitmapImage_E.Foto})


    und hier die aufgerufene Methode.

    VB.NET-Quellcode

    1. Private Function Laden(Index As Integer) As Foto_Speicher_Klasse
    2. Dim FS_T = New IO.FileStream(Bildliste(Index).FullName, IO.FileMode.Open, IO.FileAccess.Read)
    3. Dim BitmapImage_T = New BitmapImage
    4. BitmapImage_T.BeginInit()
    5. BitmapImage_T.StreamSource = FS_T
    6. BitmapImage_T.CacheOption = BitmapCacheOption.OnLoad
    7. BitmapImage_T.EndInit()
    8. FS_T.Dispose()
    9. Return New Foto_Speicher_Klasse With {.Foto = BitmapImage_T}
    10. End Function


    Wenn das so geladene Foto an das Property übergeben wird erscheint folgender Fehler:

    System.ArgumentException: ""DependencySource" muss in demselben Thread wie "DependencyObject" erstellt werden."

    Ich habe Stunden damit zugebracht die BitmapImage irgendwie in den aufrufenden Thread zu bekommen, aber bisher alles ohne Erfolg.
    Den Ladevorgang durch DecodePixelHeight zu beschleunigen ist keine Option, weil die Fotos auch in 100% Ansicht gebraucht werden.

    Wie lässt sich der Ladevorgang in den Speicher also asynchron erledigen. Oder kann man die JPG asynchron in den Speicher laden und das dann in der aufrufenedne Sub dem BitmapImage als Stream zuweisen?

    Gruß
    eddi

    Thema verschoben; Das Thema wird automatisch dort erstellt, wo man sich befindet, wenn man auf [✱ Neues Thema] klickt. ~VaporiZed

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „VaporiZed“ ()

    In welcher Zeile kommt der Fehler?

    Wenn ich nur Code 1 anschaue, dann kann ich mir nicht vorstellen, dass das Objekt im anderen Thread wabert.
    Allerdings sieht deine Foto-Klasse komisch aus, die hat ein Property mit sich selbst. Wirkt jetzt erstmal ungewöhnlich. Vielleicht hilft es dem Verständnis, wenn du deine Foto-Speicher_Klasse noch in groben Zügen angibst.

    Ich bin was Threads angeht kein Fachmann, aber ist es vielleicht möglich, dass die Property der returnten FotoSpeicherKlasse im Nebenthread fährt, denn diese wird ja nur in dem nebenläufigen Laden erzeugt und dann zugewiesen.

    Also das, was du returnst, geht in dem Hauptthread und dessen Property bleibt im Nebenthread hängen. Vielleicht kann das jemand anders bejahen oder verneinen, da könnte ich noch was lernen.
    Zuerst einmal: Ich saß die ganze Nacht dran und konnte vor ner guten Stunde eine Lösung finden. Der Wirrwar muss aber noch ins Reine gebracht werden. Dann zeige ich den Code hier mal.

    @Haudruferzappeltnoch: ähm ja, etwas ungeschickt von mir. Die Zeile mit dem Fehler ist oben gar nicht dabei. Er trit auf, wenn eine Bitmapimage aus der Collection dem Property zugewiesen wird. Das BitmapImage wird in der gezeigten Methode erstellt. Die läuft durch den Aufruf per Task.Run automatisch in einem eigenen Thread. Warum der aber bei der Übergabe an den anderen Thread so biestig ist...
    @ErfinderDesRades: Ja das ist WPF. Aber ich dachte es geht ja nicht um den WPF-Teil, sondern um den Code.

    Gruß
    eddi
    @ErfinderDesRades Dachte ich auch.

    eichseinet schrieb:

    Ja das ist WPF.
    Dann solltest Du diesen Deinen Thread auch als WPF markieren.
    Editiere den Titel dieses Deines Threads entsprechend.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Habe das Thema verschoben und neu getaggt/gelabelt.

    Ein Workaround*: Versetz das fertige Bild in Frostzustand, um klarzumachen, dass es eh nicht mehr geändert wird, damit es keine Threadzugriffskollisionen geben kann

    VB.NET-Quellcode

    1. Private Function Laden(Index As Integer) As Foto_Speicher_Klasse
    2. Dim BitmapImage_T = New BitmapImage
    3. Using FS_T As New IO.FileStream(Bildliste(Index).FullName, IO.FileMode.Open, IO.FileAccess.Read)
    4. BitmapImage_T.BeginInit()
    5. BitmapImage_T.StreamSource = FS_T
    6. BitmapImage_T.CacheOption = BitmapCacheOption.OnLoad
    7. BitmapImage_T.EndInit()
    8. End Using
    9. BitmapImage_T.Freeze() '<-- neu
    10. Return New Foto_Speicher_Klasse With {.Foto = BitmapImage_T}
    11. End Function


    * wahrscheinlich; ich hab noch nix mit WPF am Hut
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Nochmal kurz zum Thema WPF: Sorry wenn das hier offensichtlich für Verwirrung gesorgt hat. Allerdings hatte das Grundthema in meinen Augen nix mit der WPF, sondern nur mit VB.net zu tun. Daher hatte ich es halt im entsprechenden Bereich eingestellt.

    .Freeze() war tatsächlich die Lösung. Allerdings hatte ich da schon Stunden vor der Lösung erfolglos experimentiert. Da hat dann wohl sonstwas nicht gepasst.
    Hier jetzt mal der komlette Code. (man beachtet bitte, dass die Weiterschaltung bisher nur für den Test programmiert ist. Da müssen noch einige Änderungen folgen für ein korrektes Durchlaufen aller Fotos in dem Ordner!)

    Das Image im XAML

    XML-Quellcode

    1. <Image Source="{Binding Bild}" Height="{Binding Bild_Hoehe}" Grid.Row="1"/>


    Deklaration der Komponenten
    Spoiler anzeigen

    VB.NET-Quellcode

    1. #Region "Deklaration"
    2. Implements INotifyPropertyChanged
    3. Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    4. Public Sub RaisePropertyChanged(<CallerMemberName> Optional ByVal prop As String = "") 'wird bei Änderung in den Propertys aufgerufen
    5. RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(prop))
    6. End Sub
    7. Private _bild As BitmapImage
    8. Public Property Bild() As BitmapImage
    9. Get
    10. Return _bild
    11. End Get
    12. Set(ByVal value As BitmapImage)
    13. _bild = value
    14. RaisePropertyChanged()
    15. End Set
    16. End Property
    17. Private _bild_hoehe As Double = 900
    18. Public Property Bild_Hoehe() As Double
    19. Get
    20. Return _bild_hoehe
    21. End Get
    22. Set(ByVal value As Double)
    23. _bild_hoehe = value
    24. RaisePropertyChanged()
    25. End Set
    26. End Property
    27. Private Bildliste As New List(Of FileInfo)
    28. Public Index_Laden As Integer
    29. Public Property Async_aktiv() As Boolean = True 'Checkbox
    30. Public Coll_BildPuffer As New ObservableCollection(Of Foto_Speicher_Klasse)
    31. #End Region


    Fotos laden beim Programmstart

    VB.NET-Quellcode

    1. Private Async Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
    2. Me.DataContext = Me
    3. Dim Ordnerinhalt As New IO.DirectoryInfo("O:\Alpha 7 IV\2023-07-22")
    4. Bildliste = (From FI As IO.FileInfo In Ordnerinhalt.GetFiles("*.jpg", IO.SearchOption.TopDirectoryOnly) Order By FI.Name Select FI).ToList
    5. Index_Laden = 0
    6. Dim ErstesBild = Await Task.Run(Function() Laden_Async(Index_Laden))
    7. Coll_BildPuffer.Add(New Foto_Speicher_Klasse With {.Foto = ErstesBild})
    8. Bild = Coll_BildPuffer(0).Foto
    9. For i = 0 To 2
    10. Dim BitmapImage_T = Await Task.Run(Function() Laden_Async(Index_Laden))
    11. Coll_BildPuffer.Add(New Foto_Speicher_Klasse With {.Foto = BitmapImage_T})
    12. Index_Laden += 1
    13. Next
    14. End Sub


    Fotos weiterschalten und die asynchron aufgerufene Methode zum Laden des Fotos

    VB.NET-Quellcode

    1. Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)
    2. 'Button vorwärts / nächstes Bild
    3. If Index_Laden < Bildliste.Count - 1 Then
    4. If Coll_BildPuffer.Count > 2 Then
    5. Coll_BildPuffer(1).Foto = Nothing
    6. Coll_BildPuffer.RemoveAt(1)
    7. Bild = Coll_BildPuffer(1).Foto
    8. End If
    9. Index_Laden += 1
    10. Dim BitmapImage_T As New BitmapImage
    11. If Async_aktiv Then 'Checkbox => Unterschied zw. Synchron und Asynchron Laden testen
    12. BitmapImage_T = Await Task.Run(Function() Laden_Async(Index_Laden))
    13. Else
    14. BitmapImage_T = Laden_Async(Index_Laden)
    15. End If
    16. Coll_BildPuffer.Add(New Foto_Speicher_Klasse With {.Foto = BitmapImage_T})
    17. End If
    18. End Sub
    19. Private Function Laden_Async(Index As Integer) As BitmapImage
    20. Dim FS_T = New IO.FileStream(Bildliste(Index).FullName, IO.FileMode.Open, IO.FileAccess.Read)
    21. Dim BitmapImage_T As New BitmapImage
    22. BitmapImage_T.BeginInit()
    23. BitmapImage_T.StreamSource = FS_T
    24. BitmapImage_T.CacheOption = BitmapCacheOption.OnLoad
    25. BitmapImage_T.Rotation = Rotation.Rotate0
    26. BitmapImage_T.EndInit()
    27. BitmapImage_T.Freeze()
    28. FS_T.Dispose()
    29. Return BitmapImage_T
    30. End Function


    Wie man sieht gibt es noch eine Checkbox mit der zw. Synchronem und Asynchronem Aufruf umgeschaltet werden kann. So lässt sich die Geschwindigkeit schön vergleichen.

    Danke an alle Helfer

    Gruß
    eddi