Autor Beitrag
_mk_
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 23



BeitragVerfasst: Fr 05.03.21 12:04 
Hallo.

Ich stehe noch relativ weit am Anfang mit C#. Ich war vorher eher nur im Skripting-Bereich wie AutoIt unterwegs. Irgendwann waren die Möglichkeiten der Skriptsprachen leider erschöpft. Deshalb auch der Wechsel zu einer höheren Sprache.

In meinem kleinen Projekt nutzte Cassia.dll, um Sitzungsinformationen auf einem RDSH abzufragen und mir diese in grafischen Oberfläche anzeigen zu lassen. Dieses funktioniert auch schon recht zuverlässig. Wie ich es auch AutoIt kenne, friert die Benutzeroberfläche natürlich ein. Deshalb habe ich versucht, die Abfrage in einen separaten Ausführungsstrang auszulagern. Leider nur mit mäßigem Erfolg. Das Kombinationsfeld, in dem ich meine RDSHs gespeichert, bleibt jetzt nicht mehr offen und klappt ein. Aber die Oberfläche friert immer noch ein. Auch das Beschriftungsfeld, in dem der Benutzer informiert wird, dass gerade etwas passiert, wird nicht aktualisiert.

Es wäre schön, wenn Ihr mich hier in die richtige Richtung schubsen könntet.
Danke im Voraus.

Auszug aus der Klasse RemoteSession
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
public static List<SessionInfo> QueryRemoteSessions(string remoteHost)
{
    List<SessionInfo> sessionInfos = new List<SessionInfo>();

    ITerminalServicesManager manager = new TerminalServicesManager();
    ITerminalServer server = manager.GetRemoteServer(remoteHost);
    server.Open();

    if (server.IsOpen)
    {
        foreach (ITerminalServicesSession session in server.GetSessions())
        {

            if (session.ConnectionState == ConnectionState.Active)
            {
                SessionInfo sessionInfo = new SessionInfo(session.SessionId,
                                  session.UserName,
                                  session.LoginTime.ToString(),
                                  session.ClientName,
                                  session.ClientIPAddress.ToString(),
                                  session.ConnectionState.ToString());

                // account name, in <Domain>\<Account> format.
                // Debug.Print("User Account :   " + session.UserAccount.ToString());
                // 
                // Debug.Print(string.Format("Client Display : {0}x{1} with {2} bits per pixel",
                //                     session.ClientDisplay.HorizontalResolution,
                //                     session.ClientDisplay.VerticalResolution, session.ClientDisplay.BitsPerPixel));
                // Debug.Print("Connect Time :   " + session.ConnectTime.ToString());
                // Debug.Print("Current Time :   " + session.CurrentTime.ToString());
                // Debug.Print("Idle Time :      " + session.IdleTime.ToString());
                // Debug.Print("Login Time :     " + session.LoginTime.ToString());
                // Debug.Print("Stationame :     " + session.WindowStationName);
                // Debug.Print("------------");
                // Debug.Print(sessionInfo.ToString());
                sessionInfos.Add(sessionInfo);
            }
        }
    }

    server.Close();
    sessionInfos.Sort();

    return sessionInfos;
}


Nun der Quelltext in meiner WPF-Oberfläche
ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
private void RDSHList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    Ping.IsEnabled = true;
    RefreshButton.IsEnabled = true;

  string remoteHost = RDSHList.SelectedItem.ToString();
  Infolabel.Visibility = Visibility.Visible;
  Infolabel.Content = String.Format("Sitzungsinformationen von {0} werden abgefragt.\nBitte warten ...", remoteHost);
    System.Threading.Thread myThread = new System.Threading.Thread(ThreadDoWork);
    myThread.Start();
  Infolabel.Content = String.Empty;
  Infolabel.Visibility = Visibility.Hidden;
}

#region --- Threading Test
public delegate void myListviewDisplayDel();
private object myLock = new object();

private void myListviewDisplay()
{
    string remoteHost = RDSHList.SelectedItem.ToString();
    List<SessionInfo> sesionInfos = RemoteSession.QueryRemoteSessions(remoteHost);
    listview.ItemsSource = sesionInfos;
}
private void ThreadDoWork()
{
    lock (myLock)
    {
        if (!Dispatcher.CheckAccess())
        {
            Dispatcher.Invoke(new myListviewDisplayDel(myListviewDisplay));
        }
    }
}
#endregion --- Threading Test


Zuletzt bearbeitet von _mk_ am So 07.03.21 14:08, insgesamt 1-mal bearbeitet
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 05.03.21 14:18 
Das Verhalten das meine UI reagiert nur weil ich durch den Listenteil einer Combobox navigiere finde ich problematisch (Wenn ich deine Frage/Code richtig verstehe) Es sollte eigentlich erst was beim Close des Listenteils passieren. Bedenke bei reiner Tastaturbedienung wie kommt man zum gewünschten Eintrag ohne ständige Aktualisierungen auszulösen? Dazu wenn du über n Elemente navigiert hast und mehrere Threads laufen zum aktualisieren ist es halb zufällig welcher der letzte ist. Inhalt deiner Combobox ist dann nicht unbedingt passend zum Inhalt der Listview.

Gefühlt sollte das eher eine ListBox sein anstatt eine Combobox. Und wenn sich die Auswahl ändert sollte ein noch laufender Updatethread für deinen ListView abgebrochen werden bzw. solltest du irgendwie bemerken das das Ergebnis nicht mehr relevant ist und die UI durch diesen Thread nicht updaten.
Wenn es, warum auch immer, eine Combobox sein muss hilft es möglicherweise anstatt einem Thread direkt zu verwenden einen (threaded) Timer zu nehmen der einen kleinen Delay erzeugt. Hilft mindestens bei der Tastaturbedienung.

Beim Closeup der Combobox bin ich mir nicht ganz sicher könnte aber systemimmanent sein aber halte ich für unwahrscheinlich.
Vielleicht für uns zum Verständnis könntest du ausprobieren welcher Teil deines Codes das auslöst.
Hier wäre das vermutlich entweder der Zugriff auf RDSHList.SelectedItem oder listview.ItemsSource.
Wenn klar ist wo das ausgelöst wird könntest du schauen ob von diesem Control dadurch noch irgendwelche anderen Events beteiligt sind die du verdrahtet hast und was tun das den Closeup der Combobox auslösen.
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4764
Erhaltene Danke: 1052

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: Fr 05.03.21 14:53 
Hallo und :welcome:

das Einfrieren der GUI passiert deshalb in deinem Code, weil du den gesamten Thread-Code zurück in den UI-Thread (per Dispatcher.Invoke) delegiert hast. Das Abfragen der Query muß im Nebenthread passieren, nur der UI-Zugriff im UI-Thread:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
public delegate void myListviewDisplayDel(object sessionInfos);
private object myLock = new object();

private void myListviewDisplay(object sessionInfos)
{
    listview.ItemsSource = sessionInfos;
}

private void ThreadDoWork()
{
    lock (myLock)
    {
        string remoteHost = RDSHList.SelectedItem.ToString();
        List<SessionInfo> sessionInfos = RemoteSession.QueryRemoteSessions(remoteHost);

        if (!Dispatcher.CheckAccess())
        {
            Dispatcher.Invoke(new myListviewDisplayDel(myListviewDisplay), new object[] { sessionInfos });
        }
    }
}


PS: Selber sollte man keine Thread-Objekte mehr verwenden, sondern dafür gibt es die asynchrone Programmierung mittels der Task-Klasse sowie async/await: Asynchrone Programmierung mit async und await
_mk_ Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 23



BeitragVerfasst: Fr 05.03.21 15:46 
user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Das Verhalten das meine UI reagiert nur weil ich durch den Listenteil einer Combobox navigiere finde ich problematisch (Wenn ich deine Frage/Code richtig verstehe) Es sollte eigentlich erst was beim Close des Listenteils passieren. Bedenke bei reiner Tastaturbedienung wie kommt man zum gewünschten Eintrag ohne ständige Aktualisierungen auszulösen? Dazu wenn du über n Elemente navigiert hast und mehrere Threads laufen zum aktualisieren ist es halb zufällig welcher der letzte ist. Inhalt deiner Combobox ist dann nicht unbedingt passend zum Inhalt der Listview.

Danke für den Hinweis. Das hatte ich nicht bedacht, dass ein Benutzer auch nur die Tastatur bedienen könnte. Ich hoffe, dass das DropDownClosed-Ereignis hier besser geeignet ist.

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Gefühlt sollte das eher eine ListBox sein anstatt eine Combobox. ...

Warum sollte man die ListBox anstatt der ComboBox verwenden?

user profile iconRalf Jansen hat folgendes geschrieben Zum zitierten Posting springen:
Vielleicht für uns zum Verständnis könntest du ausprobieren welcher Teil deines Codes das auslöst.
Hier wäre das vermutlich entweder der Zugriff auf RDSHList.SelectedItem oder listview.ItemsSource.
Wenn klar ist wo das ausgelöst wird könntest du schauen ob von diesem Control dadurch noch irgendwelche anderen Events beteiligt sind die du verdrahtet hast und was tun das den Closeup der Combobox auslösen.

Ich habe noch keine Ahnung davon, wie ich es herausfinden kann.

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:
Hallo und :welcome:

das Einfrieren der GUI passiert deshalb in deinem Code, weil du den gesamten Thread-Code zurück in den UI-Thread (per Dispatcher.Invoke) delegiert hast. Das Abfragen der Query muß im Nebenthread passieren, nur der UI-Zugriff im UI-Thread:

PS: Selber sollte man keine Thread-Objekte mehr verwenden, sondern dafür gibt es die asynchrone Programmierung mittels der Task-Klasse sowie async/await: Asynchrone Programmierung mit async und await

Das habe ich so aus einem recht aktuellen C#-Buch (von 2019) übernommen und ein wenig angepasst. Dann werde ich mich mal als nächstes mit der Asynchronen Programmierung befassen. Ich hoffe, dass für das Verständnis nicht zu viele Grundlagen vorausgesetzt wird. Ansonsten sollten ich meine Ansprüche an dem Programm ein wenig runterschrauben, nicht so ungeduldig sein und Step-by-Step das Buch durcharbeiten. Dem Nutzer kann ich erst einmal zumuten, dass die Oberfläche für drei Sekunden einfriert.

Vielen Dank für Eure Hinweise und Tipps.
Ralf Jansen
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 4700
Erhaltene Danke: 991


VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
BeitragVerfasst: Fr 05.03.21 17:14 
Zitat:
Warum sollte man die ListBox anstatt der ComboBox verwenden?


Wenn du nun auf DropDownClosed reagierst und das Verhalten dem entspricht was du vor hattest ist mein Kritik Punkt hier nicht mehr valide. Kannst du also ignorieren.

Zitat:
Ich habe noch keine Ahnung davon, wie ich es herausfinden kann.


Eine Möglichkeit wäre vor dem zuweisen zu listview.ItemsSource zu prüfen ob in deiner ComboBox noch das Element selektiert ist mit dem du die sessionInfos ermittelt hast.
Wenn nicht dann halt nicht zuweisen. Edit: Wenn du damit lebst das deine UI einfriert kann dir das aber im Moment nicht passieren. Erst wenn du tatsächlich Parallelität zu lässt.
_mk_ Threadstarter
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Beiträge: 23



BeitragVerfasst: So 07.03.21 14:06 
Ich habe es mittels BackgroundWorker Klasse und zwei globaler Variablen hinbekommen. Die UI friert nicht mehr ein und mein Infolabel wird aktualisiert. Jetzt muss ich nur zusehen, dass ich die globalen Variablen irgendwie verliere und meinen Quelltext etwas aufräume.