Das Problem
Angenommen ein Netzwerk mit mehreren Geräten, die - wie es ihnen gefällt - gelegentlich Daten an ein Programm schicken, das das dann anzeigen soll.
Erstes Problem: die Daten kommen - bei 3 Geräten - in 3 verschiedenen Threads an.
Kein Problem, in WinForms transferiert man den Aufruf der Anzeige mittels Control.BeginInvoke in den Gui-Thread
Problematischer, wenn die Geräte häufig senden, mw. jedes 10 mal pro Sekunde. Dann hätte man sekündlich ca. 30 Thread-Transfers, und so ein Transfer ist verhältnismäßig langsam, und vor allem die Anzeige 30 mal pro Sekunde zu ändern verursacht irrsinnig ProzessorLast
Lösung
Ich schalte eine
In diese Collection können die Producer beliebig Daten hineinschmeißen, die ist threadsicher.
Und der Consumer im Gui-Thread kann gleichzeitig beliebig Daten entnehmen, ohne dass irgendwas einander in die Quere kommt.
Und weil ich die Anzeige nicht 30 mal pro Sekunde updaten will, stelle ich einen Timer zum Daten-Konsum, mw. auf 300ms, das ist reichlich schnell genug für den Menschen, der davorsitzt.
Die Producer heissen bei mir
Im TimerTick wird die Collection ausgeräumt (aber nur, wenn was drin ist) und in die Listbox gefüllt.
Returnt
Die Listbox zeigt schön, wie die Producer ganz unsortiert ihre Ergebnisse reingeschmissen haben, und man sieht auch, dass
Edit:
Neben
Angenommen ein Netzwerk mit mehreren Geräten, die - wie es ihnen gefällt - gelegentlich Daten an ein Programm schicken, das das dann anzeigen soll.
Erstes Problem: die Daten kommen - bei 3 Geräten - in 3 verschiedenen Threads an.
Kein Problem, in WinForms transferiert man den Aufruf der Anzeige mittels Control.BeginInvoke in den Gui-Thread
Problematischer, wenn die Geräte häufig senden, mw. jedes 10 mal pro Sekunde. Dann hätte man sekündlich ca. 30 Thread-Transfers, und so ein Transfer ist verhältnismäßig langsam, und vor allem die Anzeige 30 mal pro Sekunde zu ändern verursacht irrsinnig ProzessorLast
Lösung
Ich schalte eine
System.Collections.Concurrent.ConcurrentBag
als Puffer zwischen die Producer und den Consumer. In diese Collection können die Producer beliebig Daten hineinschmeißen, die ist threadsicher.
Und der Consumer im Gui-Thread kann gleichzeitig beliebig Daten entnehmen, ohne dass irgendwas einander in die Quere kommt.
Und weil ich die Anzeige nicht 30 mal pro Sekunde updaten will, stelle ich einen Timer zum Daten-Konsum, mw. auf 300ms, das ist reichlich schnell genug für den Menschen, der davorsitzt.
VB.NET-Quellcode
- Imports System.Collections.Concurrent
- Imports System.Threading
- Public Class Form1
- Private _Results As New ConcurrentBag(Of String)
- Private Const _Count As Integer = 20
- Private Sub A()
- For i = 0 To _Count
- _Results.Add("A: " & i)
- Thread.Sleep(300)
- Next
- End Sub
- Private Sub B(ByVal lst As ConcurrentBag(Of String))
- For i = 0 To _Count
- lst.Add("B: " & i)
- Thread.Sleep(400)
- Next
- End Sub
- Private Sub C(ByVal lst As ConcurrentBag(Of String), ByVal count As Integer)
- For i = 0 To count
- lst.Add("C: " & i)
- Thread.Sleep(500)
- Next
- End Sub
- Private Sub btStart_Click(ByVal sender As Object, ByVal e As EventArgs) Handles btStart.Click
- 'starte 3 Producer-Threads
- With New Action(AddressOf A)
- .BeginInvoke(AddressOf .EndInvoke, Nothing)
- End With
- With New Action(Of ConcurrentBag(Of String))(AddressOf B)
- .BeginInvoke(_Results, AddressOf .EndInvoke, Nothing)
- End With
- With New Action(Of ConcurrentBag(Of String), Integer)(AddressOf C)
- .BeginInvoke(_Results, 21, AddressOf .EndInvoke, Nothing)
- End With
- End Sub
- Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As EventArgs) Handles Timer1.Tick
- If _Results.Count = 0 Then Return
- Dim s = ""
- ListBox1.BeginUpdate()
- While _Results.TryTake(s)
- ListBox1.Items.Add(s)
- End While
- ListBox1.EndUpdate()
- End Sub
- End Class
A
, B
, C
. Es sind Methoden mit unterschiedlichen Argumenten, die in unterschiedlichen Intervallen ihre Ergebnisse letztlich in dieselbe ConcurrentBag füllen.Im TimerTick wird die Collection ausgeräumt (aber nur, wenn was drin ist) und in die Listbox gefüllt.
Returnt
.TryTake()
False
, so ist die Collection leer und der Umfüll-Vorgang fertig für dieses Mal.Die Listbox zeigt schön, wie die Producer ganz unsortiert ihre Ergebnisse reingeschmissen haben, und man sieht auch, dass
A
zuerst fertig ist, dann B
, zuletzt C
Edit:
Neben
ConcurrentBag
gibts noch weitere ConcurrentCollections: BlockingCollection
, ConcurrentQueue
, ConcurrentStack
, ConcurrentDictionary
- mit je spezifischem Verhalten.ConcurrentBag
, ConcurrentQueue
, ConcurrentStack
implementieren übrigens das sog. IProducerConsumerCollection(Of T)
- Interface.Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „ErfinderDesRades“ ()