Forum: PC-Programmierung [c#] Langsame Backgroundworker


von Pascal H. (pase-h)


Angehängte Dateien:

Lesenswert?

Hallo,
ich habe ein Problem mit den Backgroundworker in c#.

Kurz zum Programm:
Das Programm soll 100 Ports auf eingehende Verbindungen überwachen, und 
ausgeben, wer (welche IP und Port) auf den Port zugreift (61300 bis 
61400).
Um die Ports zu überwachen erstelle ich 100 Backgroundworker und 
TCPListener.

Das Programm funktioniert soweit auch, ABER:
Die Backgroundworker 0 bis 8 starten relativ schnell <1sek, und die 
restlichen (9 bis 99) brauchen ca 1 Sekunde, bis sie gestartet sind.
Dann wird erst der Port geöffnet, und auf Verbindungen gewartet…
Warte ich jetzt bis alle 100 Backgroundworker gestartet sind,  dann 
funktioniert das Programm perfekt, aber das Warten dauert halt ewig…

Daher meine Fragen:
Gibt es einfachere Möglichkeiten um 100Ports zu überwachen auf 
eingehende Verbindungen?
Warum sind die Backgroundworker so dermaßen langsam?

Die Ports werden über http://portchecker.co/ getestet, und ein umstellen 
von Debug auf Release hat auch nix gebracht.

Mein System:
OS:  Win 10 Pro x64
CPU: i7 6700k
RAM: 32GB RAM
GPU: GTX1080

Als Grundlage dient das Net.Framework 4.5.2

Und hier der Code:
1
using System;
2
using System.Collections.Generic;
3
using System.Net;
4
using System.Net.Sockets;
5
using System.ComponentModel;
6
7
namespace TCPreceiverTest
8
{
9
    class Program
10
    {
11
        private static List<TcpListener> t_listener = new List<TcpListener>();
12
        private static List<BackgroundWorker> backWorker = new List<BackgroundWorker>();
13
14
        public static int offset { get; private set; } = 61300;
15
16
        static void Main(string[] args)
17
        {
18
            for (Int32 _Port = 61300; _Port <61401;_Port++)
19
            {
20
                Console.WriteLine("Creating TCPlistener with IPADDRESS.ANY, PORT=" + _Port);
21
                t_listener.Add(new TcpListener(IPAddress.Any, _Port));
22
                Console.WriteLine("Creating Backgroundworker for TCPListener " + (_Port - offset).ToString());
23
                backWorker.Add(new BackgroundWorker());
24
                t_listener[_Port-offset].Start();
25
                Console.WriteLine("Starting TCPListener with index " + (_Port - offset) + "\t\t");
26
            }
27
            Console.ReadLine();
28
            Console.Clear();
29
            Console.SetCursorPosition(0, 0);
30
            Console.Write("Setup Backgroundworker");
31
            int index = 0;
32
            foreach (BackgroundWorker bw in backWorker)
33
            {
34
                bw.DoWork += Bw_DoWork;
35
                bw.RunWorkerAsync(index);
36
                Console.SetCursorPosition(0, 1);
37
                Console.Write("Starting Backgroundworker with index " + index + "\t\t");
38
                index++;
39
            }
40
41
            Console.ReadLine();
42
        }
43
44
        private static void Bw_DoWork(object sender, DoWorkEventArgs e)
45
        {
46
            while (true)
47
            {
48
                int index = Convert.ToInt32(e.Argument);
49
                Console.WriteLine("Worker " + Convert.ToInt32(e.Argument).ToString() + " is live! - index:" + index + " Time: " + DateTime.Now.ToString("hh:mm:ss"));
50
                TcpClient cli = t_listener[index].AcceptTcpClient();
51
                Console.SetCursorPosition(0, 8);
52
                Console.Write("Worker " + index + "got a connection!\r\nIPADDRESS: " + ((IPEndPoint)cli.Client.RemoteEndPoint).Address + " PORT: " + ((IPEndPoint)cli.Client.LocalEndPoint).Port + "\r\n");
53
                cli.Close();
54
                Console.SetCursorPosition(0, 10);
55
                Console.Write("Closed Port " + (index + offset).ToString());
56
            }
57
        }
58
    }
59
}

Grüße
Pascal

: Bearbeitet durch User
von Draco (Gast)


Lesenswert?

Pascal H. schrieb:
> CPU: i7 6700k

...und...

Pascal H. schrieb:
> Die Backgroundworker 0 bis 8 starten relativ schnell <1sek,

was sagt dir das? ;-)

von Kaj (Gast)


Lesenswert?

Pascal H. schrieb:
> Das Programm soll 100 Ports auf eingehende Verbindungen überwachen, und
> ausgeben, wer (welche IP und Port) auf den Port zugreift (61300 bis
> 61400).

Pascal H. schrieb:
> for (Int32 _Port = 61300; _Port <61401;_Port++)
Sicher dass das genau 100 Ports sind? :P

Darüberhinaus: Wenn man sich das Bild ansieht, dann sieht man, dass die 
Worker 0 bis 8 (0, 2, 1, 6, 3, 7, 4, 5) nicht linear gestartet werden, 
alle anderen aber sehr wohl (8, 9, 10, 11, 12, 13, ...).
Was könntest du daraus schließen?

von Draco (Gast)


Lesenswert?

Kaj schrieb:
> Darüberhinaus: Wenn man sich das Bild ansieht, dann sieht man, dass die
> Worker 0 bis 8 (0, 2, 1, 6, 3, 7, 4, 5) nicht linear gestartet werden,
> alle anderen aber sehr wohl (8, 9, 10, 11, 12, 13, ...).
> Was könntest du daraus schließen?

Das ist normal, die ersten acht Ports werden ihren Threads den logischen 
CPU-Cores zugewiesen. Der eine arbeitet schneller, der andere langsamer. 
Der TE könnte ja mal ganz einfach probieren die Ports auf nur drei oder 
vier zu minimieren - dabei mal den einzelnen Cores zusehen, wie deren 
Belastung / Zuweisung ist. Dann wird er verstehen wie ein 
Backgroundworker arbeitet. Er hat da quasi 100 Threads am laufen - das 
ist schon derb!

Ich würde das komplett anders anstellen. Ein (1!!) Thread / 
Backgroundworker der den gesamten Adressbereich async abscannt und dann 
in einem anderen Thread die einzelnen Ports, die einen Zugriff haben, 
ausklabüstern.

von c-hater (Gast)


Lesenswert?

Pascal H. schrieb:

> Das Programm soll 100 Ports auf eingehende Verbindungen überwachen, und
> ausgeben, wer (welche IP und Port) auf den Port zugreift (61300 bis
> 61400).
> Um die Ports zu überwachen erstelle ich 100 Backgroundworker und
> TCPListener.

So ein krasser Unsinn. Typischer Fall von sinnloser 
Threadvervielfachung, weil ich es nicht besser weiß...

Für sowas nimmt man natürlich einfache Sockets und nutzt dabei aus, dass 
die sowieso asynchron agieren (können). Die nötigen Warte-Threads 
erzeugen die dann selber.

Man macht also einfach 100(?! ;o) Sockets auf, ruft deren Methoden Bind, 
Listen und BeginAccept() auf und schon ist man durch mit dem Thema.

von c.m. (Gast)


Lesenswert?

ich kenne c# nicht, aber kann es sein das die initiale größe der listen 
zu klein ist, und während der laufzeit "umständlich" vergrößert werden 
muss?
1
private static List<TcpListener> t_listener = new List<TcpListener>();
2
private static List<BackgroundWorker> backWorker = new List<BackgroundWorker>();

kann man in der letzten runden klammer die initiale größe angeben? mach 
mal 100 :)

von Draco (Gast)


Lesenswert?

c.m. schrieb:
> ich kenne c# nicht, aber kann es sein das die initiale größe der listen
> zu klein ist, und während der laufzeit "umständlich" vergrößert werden
> muss?

Man macht einfach keine 100 Threads und 100 Sockets gleichzeitig auf!

So... das geht... ist aber ebenfalls Synchron:
1
using System;
2
using System.Net;
3
using System.Net.Sockets;
4
using System.Threading;
5
6
class ListenPorts
7
{
8
    Socket[] scon;
9
    IPEndPoint[] ipPoints;
10
    internal ListenPorts(IPEndPoint[] ipPoints)
11
    {
12
        this.ipPoints = ipPoints;
13
        scon = new Socket[ipPoints.Length];
14
    }
15
16
    public void beginListen()
17
    {
18
        for (int i = 0; i < ipPoints.Length; i++)
19
        {
20
            scon[i] = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
21
            try
22
            {
23
                scon[i].Bind(ipPoints[i]);
24
                Thread thread = new Thread(threadListen);
25
                thread.Start(scon[i]);
26
                Console.WriteLine("Socket: " + ipPoints[i].Port.ToString() + " is alive at " + DateTime.Now.ToString("hh.mm.ss:fff"));
27
            }
28
            catch
29
            {
30
                Console.WriteLine("Socket: " + ipPoints[i].Port.ToString() + " ERROR " + DateTime.Now.ToString("hh.mm.ss"));
31
            }
32
        }
33
34
    }
35
36
    public void threadListen(object objs)
37
    {
38
        Socket scon = objs as Socket;
39
        byte[] data = new byte[1024];
40
        IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
41
        EndPoint Remote = (EndPoint)(sender);
42
        try
43
        {
44
            scon.Listen(100);
45
            Socket newSocket = scon.Accept();
46
            newSocket.ReceiveFrom(data, ref Remote);
47
            // scon.ReceiveFrom(data, ref Remote);
48
        }
49
        catch (SocketException ex)
50
        {
51
            Console.WriteLine(ex.Message);
52
        }
53
        Console.WriteLine(scon.LocalEndPoint.ToString() + "IP {0}: ", Remote.ToString());
54
    }
55
56
}
57
58
class Program
59
{
60
    static void Main(string[] args)
61
    {
62
        IPEndPoint[] ipPoints = new IPEndPoint[101];
63
64
        for (int i = 61300; i <= 61400; i++)
65
        {
66
            IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), i);
67
            ipPoints[i-61300] = ipPoint;
68
        }
69
        
70
        ListenPorts lp = new ListenPorts(ipPoints);
71
        Console.WriteLine("Begin Listen");
72
        lp.beginListen();
73
    }
74
}

Ich mach jetzt mal fix nen Asynchronen...

von D. I. (Gast)


Lesenswert?

Draco schrieb:
> Man macht einfach keine 100 Threads und 100 Sockets gleichzeitig auf!

Und was macht dein Code?

von Draco (Gast)


Lesenswert?

100 Threads auf ich weiß. :-D Mit den unverwalteten sinds sogar 114 ;-) 
Ich sagte ja, ich mach noch ein mit asynchron.

von Georg B. (diereinegier)


Lesenswert?

Wie schon gesagt, gibt es wahrscheinlich einen besseren Weg als für 
jeden Port einen eigenen Thread zu starten. Aber nicht unbedingt...

Bestimmt lohnt es sich, die Klasse ThreadPool anzusehen. Es gibt nur 
einen ThreadPool pro Prozeß und mit den statischen Methoden der Klasse 
läßt sich grob steuern, wieviele Threads gleichzeitig vorgehalten 
werden. Wenn die Threads alle I/O-Bound sind, dann kann man schon sehr 
viele erzeugen, ohne den Rechner lahmzulegen.

Der ThreadPool beobachtet die Threads, die er erzeugt. Wenn die nach 
einer gewissen Zeit auf I/O warten, dann erlaubt er sich, neue zu 
starten. Die genaue Strategie ist auch davon abhängig, ob .net auf einem 
Client- oder Server-Betriebssystem läuft.

von js (Gast)


Lesenswert?

die 100 threads sind kein Problem für Windows. Aber die BackgroundWorker 
sind komplexere Dinger die für Kommunikation mit dem UI Thread gedacht 
sind. Die Version von Draco ist ok, die meiste Zeit warten die vielen 
Threads auf ein IO Event und kosten nix.
Dieses Beispiel dürfte ein Paradebeispiel für Javascript mit Node.js 
sein weil das von Hause aus asynchron und Eventgesteuert arbeitet.

von guest (Gast)


Lesenswert?


von Torsten C. (torsten_c) Benutzerseite


Lesenswert?

guest schrieb:
> Ist ja nicht so, daß .NET nicht auch asyncron könnte
Schreibt ja Draco:
> Ich sagte ja, ich mach noch ein mit asynchron.

Aber da kommt dann jeweils ein IAsyncResult raus,
also werden es wieder 100 Threads oder Tasks.
Nur dass man die nicht so offensichtlich selbst erzeugt.

Wobei ich den Unterschied zwischen Threads und Tasks nicht spontan 
erklären könnte. Vielleicht brauchen Tasks weniger Ressourcen als 
Threads?

: Bearbeitet durch User
von Oliver R. (superberti)


Lesenswert?

Hi,

.NET zeigt dieses Verhalten stets bei Threads, die sich aus dem 
ThreadPool bedienen. Dazu gehören auch der BackgroundWorker sowie das 
komplette Task-API. Sind alle logischen Kerne mit Threads ausgelastet, 
dann wird jeder weitere Thread nur noch zeitverzögert gestartet, so 
500-1000 ms nach meinen Beobachtungen.
Dies macht ja auch normalerweise Sinn, um Überlastungen durch zu viele 
gleichzeitige Threads zu vermeiden. Wahrscheilich also eine 
Optimierungsstrategie von .NET.
Umgehen kann man dies, indem man direkt die Thread-Klasse benutzt. Dies 
macht hier und da mehr Arbeit, aber das beobachtete Verhalten tritt dort 
nicht mehr auf, und das Programm verhält sich wie früher das WinAPI.
Ob das alles Sinn macht steht selbstverständlich auf einem anderen 
Blatt.
Ich habe jedenfalls diese Methode Threads zu erzeugen, nur ein einziges 
mal benötigt.

Gruß,
Oliver

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.