Thread, MultiThreadin, usw

  • VB.NET
  • .NET 5–6

Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von Coldfire.

    Thread, MultiThreadin, usw

    Moin moin

    ich arbeite gerade daran, ein älteres Programm durch ein "Rebuild" zu modernisieren, von .Net FW4.6.1 zu .Net 8. Dabei möchte ich verschiedene Funktionen in externe DLLs auslagern. Die erste DLL habe ich bereits erfolgreich erstellt, und sie berechnet Durchschnittswerte. Die Hauptanwendung übermittelt dabei ein Jahres-Wert aus einer ComboBox an die DLL, welche die Berechnungen für die vorliegenden XML-Dateien vornimmt und die Ergebnisse zurückliefert.

    Vorab habe ich mit einem kleinen Converter die alten XML-Dateien an das neue Modell in der DLL angepasst.
    Nun habe ich jedoch gelesen, dass man bei der Arbeit mit DLLs besonders auf das Thema Threading und Multithreading achten muss. Ich verstehe allerdings noch nicht ganz, wann und warum ich beispielsweise so etwas wie ein SyncLock _lock verwenden muss.

    Könntet Ihr mir dabei helfen, den Zusammenhang besser zu verstehen und zu wissen, wann genau solche Synchronisationsmechanismen erforderlich sind?

    Hier der Code der DLL(für .Net 8 und .Net FW 4.6.1 kompiliert)
    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class WeatherData
    2. ' Keine Singleton-Instanz, da als Datencontainer für die XML-Inhalte verwendet wird!
    3. Public Property WeatherDay As String '' Keine Berechnungen aber zum speichern von neuen Daten erforderlich!
    4. Public Property WeatherNight As String '' Keine Berechnungen aber zum speichern von neuen Daten erforderlich!
    5. Public Property WeatherDate As DateTime
    6. Public Property RainDay As Double?
    7. Public Property SnowDay As Double?
    8. ' nicht alle aufgelistet !!
    9. End Class
    10. 'Klasse für Array von Wetterdaten
    11. Public Class WeatherDataArray
    12. Inherits List(Of WeatherData)
    13. End Class


    VB.NET-Quellcode

    1. Public Class ClimaAverage
    2. ' Keine Singleton-Instanz, da als Datencontainer für die Berechnungen verwendet wird!
    3. Public Property Period As Integer
    4. Public Property AverageRain As Double?
    5. Public Property AverageSnowDay As Double?
    6. ' nicht alle aufgelistet !!
    7. Public Property EntryCount As Integer
    8. End Class


    VB.NET-Quellcode

    1. Public Class ConfigService
    2. Inherits PropertyChange
    3. ' Singleton-Instanz, Threadsicher durch statische Initialisierung
    4. Public Shared ReadOnly Instance As New ConfigService()
    5. ' Pfad der späteren Hauptanwendung und Datenpfad für XML-Dateien
    6. Private Const MyBasePath As String = "WeatherApp" ' Für LogFiles, ConfigFiles etc.
    7. Private Const MyDataPath As String = "WeatherApp\Data"
    8. ' Definieren der Fehlermeldungen als Konstanten
    9. Private Const AccessDeniedMessage As String = "Zugriff verweigert für das Verzeichnis: {0}"
    10. Private Const DirectoryCreationErrorMessage As String = "Fehler beim Erstellen des Verzeichnisses: {0}"
    11. ' --- Eigenschaftsbereiche für Verzeichnisse ---
    12. Public ReadOnly Property BaseDirectory As String
    13. Get
    14. Return CreateDirectorySafe(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MyBasePath))
    15. End Get
    16. End Property
    17. Public ReadOnly Property DataDirectory As String
    18. Get
    19. Return CreateDirectorySafe(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), MyDataPath))
    20. End Get
    21. End Property
    22. ' --- Sichere Verzeichniserstellungsmethode mit Fehlerbehandlung ---
    23. Private Function CreateDirectorySafe(path As String) As String
    24. Try
    25. If Not Directory.Exists(path) Then
    26. Directory.CreateDirectory(path)
    27. End If
    28. Return path
    29. Catch ex As UnauthorizedAccessException
    30. Throw New Exception(String.Format(AccessDeniedMessage, path), ex)
    31. Catch ex As Exception
    32. Throw New Exception(String.Format(DirectoryCreationErrorMessage, path), ex)
    33. End Try
    34. End Function
    35. ' --- Properties für dynamische Daten (XML-Dateien, Jahr, Monat) ---
    36. Private _xmlDataDirectory As String
    37. Public Property XmlDataDirectory As String
    38. Get
    39. If String.IsNullOrEmpty(_xmlDataDirectory) Then
    40. _xmlDataDirectory = DataDirectory
    41. End If
    42. Return _xmlDataDirectory
    43. End Get
    44. Set(value As String)
    45. If _xmlDataDirectory <> value Then
    46. _xmlDataDirectory = value
    47. OnPropertyChanged(NameOf(XmlDataDirectory))
    48. End If
    49. End Set
    50. End Property
    51. Private _currentYear As Integer
    52. Public Property CurrentYear As Integer
    53. Get
    54. Return _currentYear
    55. End Get
    56. Set(value As Integer)
    57. If _currentYear <> value Then
    58. _currentYear = value
    59. OnPropertyChanged(NameOf(CurrentYear))
    60. End If
    61. End Set
    62. End Property
    63. Private _currentMonth As Integer
    64. Public Property CurrentMonth As Integer
    65. Get
    66. Return _currentMonth
    67. End Get
    68. Set(value As Integer)
    69. If _currentMonth <> value Then
    70. _currentMonth = value
    71. OnPropertyChanged(NameOf(CurrentMonth))
    72. End If
    73. End Set
    74. End Property
    75. End Class




    Spoiler anzeigen

    VB.NET-Quellcode

    1. Public Class CalculatedAverages
    2. ' Singleton-Instanz, Thread-sicher durch statische Initialisierung
    3. Public Shared ReadOnly Instance As New CalculatedAverages()
    4. ' Um die monatlichen Durchschnittswerte zu speichern
    5. Private ReadOnly _monthlyAverages As New List(Of ClimaAverage)()
    6. ' Dienste laden
    7. Private ReadOnly _configServiceInstance As ConfigService = ConfigService.Instance
    8. ' Getter für die monatlichen Durchschnittswerte
    9. Public Function GetMonthlyAverages() As List(Of ClimaAverage)
    10. Return _monthlyAverages
    11. End Function
    12. Public Function GetXmlFiles(selectedYear As Integer, month As Integer) As List(Of String)
    13. ' Formatieren von Jahr und Monat als String im Format "MM-YYYY.xml"
    14. Dim monthPattern As String = $"{month:00}-{selectedYear}.xml"
    15. Dim directoryPath As String = _configServiceInstance.XmlDataDirectory
    16. ' Debug: Ausgabe des Suchmusters
    17. Console.WriteLine($"Suche nach Dateien mit Muster: {monthPattern} in Verzeichnis: {directoryPath}")
    18. ' Abrufen der Dateien, die dem Muster entsprechen
    19. Dim files = Directory.GetFiles(directoryPath, monthPattern, SearchOption.TopDirectoryOnly).ToList()
    20. ' Debug-Ausgabe der gefundenen Dateien
    21. Console.WriteLine($"Gefundene Dateien für {month:00}-{selectedYear}: {String.Join(", ", files)}")
    22. Return files
    23. End Function
    24. Public Sub CallCalculation()
    25. _monthlyAverages.Clear()
    26. Console.WriteLine($"Berechnungen für das Jahr: {_configServiceInstance.CurrentYear}")
    27. For month As Integer = 1 To 12
    28. ' Aufruf von GetXmlFiles mit `currentYear` und `month`
    29. Dim files As List(Of String) = GetXmlFiles(_configServiceInstance.CurrentYear, month)
    30. If files.Count > 0 Then
    31. ' Summen und Zähler für die Berechnungen
    32. Dim monthlyRainDay As Double = 0
    33. Dim monthlySnowDay As Double = 0
    34. Dim monthlySnowNight As Double = 0
    35. Dim monthlyHumidiMinDay As Double = 0
    36. Dim monthlyHumidiMaxDay As Double = 0
    37. Dim monthlyHumidiMinNight As Double = 0
    38. Dim monthlyHumidiMaxNight As Double = 0
    39. Dim monthlyTempDay As Double = 0
    40. Dim monthlyTempNight As Double = 0
    41. Dim monthlyWindDay As Double = 0
    42. Dim monthlyWindNight As Double = 0
    43. Dim count As Integer = 0
    44. For Each filename As String In files
    45. Console.WriteLine($"Verarbeite Datei: {filename}")
    46. ' Wetterdaten deserialisieren
    47. Dim serializer As New XmlSerializer(GetType(WeatherDataArray))
    48. Using fs As New FileStream(filename, FileMode.Open)
    49. Dim data As WeatherDataArray = CType(serializer.Deserialize(fs), WeatherDataArray)
    50. If data.Count > 0 Then
    51. count += data.Count
    52. ' Werte summieren
    53. Dim rainSum As Double = CDbl(data.Sum(Function(wd) wd.RainDay))
    54. Dim snowDaySum As Double = CDbl(data.Sum(Function(wd) wd.SnowDay))
    55. Dim snowNightSum As Double = CDbl(data.Sum(Function(wd) wd.SnowNight))
    56. Dim humidiMinDaySum As Double = CDbl(data.Sum(Function(wd) wd.HumidiMinDay))
    57. Dim humidiMaxDaySum As Double = CDbl(data.Sum(Function(wd) wd.HumidiMaxDay))
    58. Dim humidiMinNightSum As Double = CDbl(data.Sum(Function(wd) wd.HumidiMinNight))
    59. Dim humidiMaxNightSum As Double = CDbl(data.Sum(Function(wd) wd.HumidiMaxNight))
    60. Dim tempDaySum As Double = CDbl(data.Sum(Function(wd) wd.TempDay))
    61. Dim tempNightSum As Double = CDbl(data.Sum(Function(wd) wd.TempNight))
    62. Dim windDaySum As Double = CDbl(data.Sum(Function(wd) wd.WindDay))
    63. Dim windNightSum As Double = CDbl(data.Sum(Function(wd) wd.WindNight))
    64. ' Zu den monatlichen Summen hinzufügen
    65. monthlyRainDay += rainSum
    66. monthlySnowDay += snowDaySum
    67. monthlySnowNight += snowNightSum
    68. monthlyHumidiMinDay += humidiMinDaySum
    69. monthlyHumidiMaxDay += humidiMaxDaySum
    70. monthlyHumidiMinNight += humidiMinNightSum
    71. monthlyHumidiMaxNight += humidiMaxNightSum
    72. monthlyTempDay += tempDaySum
    73. monthlyTempNight += tempNightSum
    74. monthlyWindDay += windDaySum
    75. monthlyWindNight += windNightSum
    76. Else
    77. Console.WriteLine($"Keine Wetterdaten in Datei {filename} gefunden.")
    78. End If
    79. End Using
    80. Next
    81. ' Durchschnittswerte berechnen
    82. If count > 0 Then
    83. Dim avgRain = Math.Round(monthlyRainDay / count, 2)
    84. Dim avgSnowDay = Math.Round(monthlySnowDay / count, 2)
    85. Dim avgSnowNight = Math.Round(monthlySnowNight / count, 2)
    86. Dim avgHumidiMinDay = Math.Round(monthlyHumidiMinDay / count, 2)
    87. Dim avgHumidiMaxDay = Math.Round(monthlyHumidiMaxDay / count, 2)
    88. Dim avgHumidiMinNight = Math.Round(monthlyHumidiMinNight / count, 2)
    89. Dim avgHumidiMaxNight = Math.Round(monthlyHumidiMaxNight / count, 2)
    90. Dim avgTempDay = Math.Round(monthlyTempDay / count, 2)
    91. Dim avgTempNight = Math.Round(monthlyTempNight / count, 2)
    92. Dim avgWindDay = Math.Round(monthlyWindDay / count, 2)
    93. Dim avgWindNight = Math.Round(monthlyWindNight / count, 2)
    94. _monthlyAverages.Add(New ClimaAverage() With {
    95. .Period = month,
    96. .AverageRain = avgRain,
    97. .AverageSnowDay = avgSnowDay,
    98. .AverageSnowNight = avgSnowNight,
    99. .AverageHumidiMinDay = avgHumidiMinDay,
    100. .AverageHumidiMaxDay = avgHumidiMaxDay,
    101. .AverageHumidiMinNight = avgHumidiMinNight,
    102. .AverageHumidiMaxNight = avgHumidiMaxNight,
    103. .AverageTempDay = avgTempDay,
    104. .AverageTempNight = avgTempNight,
    105. .AverageWindDay = avgWindDay,
    106. .AverageWindNight = avgWindNight
    107. })
    108. Else
    109. Console.WriteLine($"Monat {month}: Keine Daten gefunden, Durchschnittswerte werden nicht berechnet.")
    110. End If
    111. Else
    112. Console.WriteLine($"Keine Wetterdaten für {month:00}-{_configServiceInstance.CurrentYear} gefunden.")
    113. End If
    114. Next
    115. Console.WriteLine("Berechnungen abgeschlossen.")
    116. End Sub
    117. Public Function CalculateYearlyAverages() As ClimaAverage
    118. Dim yearlyAverage As New ClimaAverage With {
    119. .Period = _configServiceInstance.CurrentYear
    120. }
    121. If _monthlyAverages.Count > 0 Then
    122. Dim totalRain As Double = 0
    123. Dim totalSnowDay As Double = 0
    124. Dim totalSnowNight As Double = 0
    125. Dim totalHumidiMinDay As Double = 0
    126. Dim totalHumidiMaxDay As Double = 0
    127. Dim totalHumidiMinNight As Double = 0
    128. Dim totalHumidiMaxNight As Double = 0
    129. Dim totalTempDay As Double = 0
    130. Dim totalTempNight As Double = 0
    131. Dim totalWindDay As Double = 0
    132. Dim totalWindNight As Double = 0
    133. Dim countRain As Integer = 0
    134. Dim countSnowDay As Integer = 0
    135. Dim countSnowNight As Integer = 0
    136. Dim countHumidiMinDay As Integer = 0
    137. Dim countHumidiMaxDay As Integer = 0
    138. Dim countHumidiMinNight As Integer = 0
    139. Dim countHumidiMaxNight As Integer = 0
    140. Dim countTempDay As Integer = 0
    141. Dim countTempNight As Integer = 0
    142. Dim countWindDay As Integer = 0
    143. Dim countWindNight As Integer = 0
    144. For Each avg In _monthlyAverages
    145. totalRain += CDbl(avg.AverageRain)
    146. totalSnowDay += CDbl(avg.AverageSnowDay)
    147. totalSnowNight += CDbl(avg.AverageSnowNight)
    148. totalHumidiMinDay += CDbl(avg.AverageHumidiMinDay)
    149. totalHumidiMaxDay += CDbl(avg.AverageHumidiMaxDay)
    150. totalHumidiMinNight += CDbl(avg.AverageHumidiMinNight)
    151. totalHumidiMaxNight += CDbl(avg.AverageHumidiMaxNight)
    152. totalTempDay += CDbl(avg.AverageTempDay)
    153. totalTempNight += CDbl(avg.AverageTempNight)
    154. totalWindDay += CDbl(avg.AverageWindDay)
    155. totalWindNight += CDbl(avg.AverageWindNight)
    156. ' Inkrementieren der Zähler
    157. countRain += If(avg.AverageRain > 0, 1, 0)
    158. countSnowDay += If(avg.AverageSnowDay > 0, 1, 0)
    159. countSnowNight += If(avg.AverageSnowNight > 0, 1, 0)
    160. countHumidiMinDay += If(avg.AverageHumidiMinDay > 0, 1, 0)
    161. countHumidiMaxDay += If(avg.AverageHumidiMaxDay > 0, 1, 0)
    162. countHumidiMinNight += If(avg.AverageHumidiMinNight > 0, 1, 0)
    163. countHumidiMaxNight += If(avg.AverageHumidiMaxNight > 0, 1, 0)
    164. countTempDay += If(avg.AverageTempDay > 0, 1, 0)
    165. countTempNight += If(avg.AverageTempNight > 0, 1, 0)
    166. countWindDay += If(avg.AverageWindDay > 0, 1, 0)
    167. countWindNight += If(avg.AverageWindNight > 0, 1, 0)
    168. Next
    169. ' Durchschnittswerte berechnen
    170. yearlyAverage.AverageRain = Math.Round(totalRain / countRain, 2)
    171. yearlyAverage.AverageSnowDay = Math.Round(totalSnowDay / countSnowDay, 2)
    172. yearlyAverage.AverageSnowNight = Math.Round(totalSnowNight / countSnowNight, 2)
    173. yearlyAverage.AverageHumidiMinDay = Math.Round(totalHumidiMinDay / countHumidiMinDay, 2)
    174. yearlyAverage.AverageHumidiMaxDay = Math.Round(totalHumidiMaxDay / countHumidiMaxDay, 2)
    175. yearlyAverage.AverageHumidiMinNight = Math.Round(totalHumidiMinNight / countHumidiMinNight, 2)
    176. yearlyAverage.AverageHumidiMaxNight = Math.Round(totalHumidiMaxNight / countHumidiMaxNight, 2)
    177. yearlyAverage.AverageTempDay = Math.Round(totalTempDay / countTempDay, 2)
    178. yearlyAverage.AverageTempNight = Math.Round(totalTempNight / countTempNight, 2)
    179. yearlyAverage.AverageWindDay = Math.Round(totalWindDay / countWindDay, 2)
    180. yearlyAverage.AverageWindNight = Math.Round(totalWindNight / countWindNight, 2)
    181. Else
    182. Console.WriteLine("Keine monatlichen Durchschnittswerte vorhanden.")
    183. End If
    184. Return yearlyAverage
    185. End Function
    186. End Class

    Asperger Autistin. Brauche immer etwas um gewisse Sachen zu verstehen. :huh:
    Wenn mehrere Tasks parallel arbeiten und auf ein und dersselben externen Ressource schreibend tätig werden, kann es sein, dass Änderungen übersprungen werden.
    Beispiel: Gugge mal was hier schief läuft.

    Wenn man die Ressource bei Zugriff sperrt mit einem Lock, dann stellt das wieder sicher, dass alle Änderungen wie vorgesehen ankommen.
    Wenn Multithreading für Effizienz genutzt wird, dann ist ein Lock kontraproduktiv bzw. nicht die richtige Wahl, um eine bestehendes Problem zu lösen.
    Das heißt in dem Beispiel-Problem, wurde kein Lock als Lösung eingesetzt (obwohl das auch Ungereimtheiten beseitigen würde), sondern die Änderungen an externen Variablen auf interne (ne eigentlich pseudo-interne) Variablen umgeleitet.

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

    Solange Du keine Nebenläufigkeit verwendest (ich erkenne zumindest keine in Deinem Code), musst Du da auch nix machen/beachten. Synclock ist wenn Du mit Nebenläufigkeit arbeitest und sich Situationen ergeben könnten, in denen eine oder mehrere Codezeilen von mehreren Stellen tatsächlich nahezu gleichzeitig aufgerufen/erreicht werden könnten und Du sicherstellen musst, dass die betroffenen Codezeile(n) nur einmalig abgearbeitet werden dürfen. Insbesondere trifft das auf Instanzerstellungen zu.
    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.