Timer threadübergreifend starten/stoppen??

  • VB.NET

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

    Timer threadübergreifend starten/stoppen??

    Hallo zusammen,

    ich habe vor einiger Zeit begonnen, einen Server-Manager zu programmieren, der auf einem Root-Server läuft. Weitere Infor hier: List Of <Class>, vorherige Elemente werden überschrieben
    Soweit funktioniert auch (fast) alles.

    Als Vorabinfo zu meinem aktuellen Problem noch folgendes:
    Der Servermanager kann sowohl lokal über Buttons usw. direkt bedient werden, als auch über ein Web-Interface gesteuert werden. Für die Kommunikation mit dem Web-Interface läuft ein eigener Thread. Es gibt eine Funktion, die die Server updaten soll. Dazu muss ein externes Kommandozeilen-Programm aufgerufen werden, das den Server updatet und sich danach beendet.
    Um festzustellen, wann der Updater fertig ist, läuft ein Timer, der zyklisch prüft, ob der Prozess beendet ist (vielleicht nicht die beste Variante, funktioniert aber). Das Update wird über die Funktion UpdateGameServer(..) gestartet, die eben den Updater und danach den Timer startet. Diese Funktion wird sowohl lokal über einen Button, als auch über das Web-Interface aufgerufen. Der Updater wird in beiden Fällen gestartet, der Timer allerdings nur, wenn die Funktion lokal über den Button ausgeführt wird.

    Daher meine Frage: Kann ein Timer standardmäßig nicht threadübergreifend gestartet werden? Wie bekomme ich das hin, dass der Timer auch aus dem anderen Thread heraus gestartet wird (bzw warum wird die Funktion ausgeführt, nur das Starten des Timers nicht?)?

    Vielen Dank schon mal im Vorraus!

    cya Igel


    Form Load:

    VB.NET-Quellcode

    1. Public Class Hauptfenster
    2. Private Delegate Sub UpdateTextDelegate(RTB As RichTextBox, txt As String)
    3. Private Sub Hauptfenster_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    4. Me.Text = "ServerManager - " & "V" & FileVersionInfo.GetVersionInfo(IO.Path.GetFileName(Application.ExecutablePath)).FileVersion.ToString
    5. If System.Diagnostics.Process.GetProcessesByName("ServerManager").Count > 1 Then
    6. Dim proc As Process = System.Diagnostics.Process.GetCurrentProcess()
    7. MessageBox.Show(Me, "Eine Instanz des ServerManagers läuft bereits.", "ServerManager", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
    8. proc.Kill()
    9. End If
    10. CheckForIllegalCrossThreadCalls = False
    11. LoadServerList()
    12. TCPServer = New TCPControl("127.0.0.1", 4305)
    13. AddHandler TCPServer.MessageReceived, AddressOf ValidateRequestAndRespond
    14. TxtServername.Text = "-- kein Server ausgewählt --"
    15. If ProgramData.Servers.Count = 0 Then
    16. ServerBearbeitenToolStripMenuItem.Enabled = False
    17. ServerLöschenToolStripMenuItem.Enabled = False
    18. End If
    19. If ProgramData.Settings.AllowRemoteControl = True Then
    20. TCPServer.StartServer()
    21. Else
    22. TCPServer.StopServer()
    23. TaReceive.Enabled = False
    24. End If
    25. TimGameServerMonitoring.Enabled = True
    26. TimApplicationRunningCheck.Enabled = True ' UpdateProcessTimer - 5 sec
    27. .
    28. .
    29. .
    30. End Sub
    31. .
    32. .


    Timer:

    VB.NET-Quellcode

    1. Private Sub TimApplicationRunningCheck_Tick(sender As Object, e As EventArgs) Handles TimApplicationRunningCheck.Tick ' 5 Sekunden
    2. Dim updateCount As Integer = 0
    3. Console.WriteLine("TimApplicationRunningCheck_Tick...")
    4. For srvIdx As Integer = 0 To ProgramData.Servers.Count - 1
    5. If Not ProcessIsRunning(ProgramData.Servers(srvIdx).SteamCMDPID) Then
    6. If ProgramData.Servers(srvIdx).Serverstate = ServerStatus.UpdatingStart Then
    7. Dim status As Boolean = CBool(StartGameServer(ProgramData.Servers(srvIdx).ID, True))
    8. If status = True Then
    9. SetServerControls(ServerStatus.Running, ProgramData.Servers(srvIdx).ID)
    10. Else
    11. SetServerControls(ServerStatus.Stopped, ProgramData.Servers(srvIdx).ID)
    12. End If
    13. ProgramData.Servers(srvIdx).SteamCMDPID = 0
    14. ElseIf ProgramData.Servers(srvIdx).Serverstate = ServerStatus.Updating Then
    15. SetServerControls(ServerStatus.Stopped, ProgramData.Servers(srvIdx).ID)
    16. ProgramData.Servers(srvIdx).SteamCMDPID = 0
    17. End If
    18. Else
    19. updateCount += 1
    20. End If
    21. Next
    22. If updateCount = 0 Then
    23. TimApplicationRunningCheck.Enabled = False
    24. End If
    25. End Sub


    Update-Funktion:

    VB.NET-Quellcode

    1. Function UpdateGameServer(ServerID As Integer, Optional ByVal SilentMode As Boolean = False) As Integer
    2. Dim procID As Integer = 0
    3. Dim srvIdx As Integer = ProgramData.GetSrvIdx(ServerID)
    4. If System.IO.File.Exists(ProgramData.Settings.SteamCmdPath) Then
    5. Dim procProp As New ProcessStartInfo
    6. If Not ProcessIsRunning(ProgramData.Servers(srvIdx).SteamCMDPID) Then
    7. Try
    8. procProp.FileName = ProgramData.Settings.SteamCmdPath
    9. procProp.Arguments = "+login anonymous +force_install_dir " & ProgramData.Servers(srvIdx).Path & " +app_update " & ProgramData.Settings.AppID & " validate +quit"
    10. procProp.WindowStyle = ProcessWindowStyle.Minimized
    11. procID = Process.Start(procProp).Id
    12. ProgramData.Servers(srvIdx).SteamCMDPID = procID
    13. Hauptfenster.TimApplicationRunningCheck.Enabled = True
    14. ServerLogFiles(srvIdx).Add("SERVER STATUS", "UpdateGameServer(): Server updating")
    15. Catch ex As Exception
    16. procID = 0
    17. If SilentMode = False Then MsgBox("Es ist ein unbekannter Fehler beim Starten von SteamCMD aufgetreten!", vbCritical, "Update-Status")
    18. ServerLogFiles(srvIdx).Add("SERVER STATUS", "UpdateGameServer(): Unkown error occured!")
    19. End Try
    20. Else
    21. procID = 0
    22. If SilentMode = False Then MsgBox("Es läuft bereits eine Update-Routine.", vbExclamation, "Update-Status")
    23. ServerLogFiles(srvIdx).Add("SERVER STATUS", "UpdateGameServer(): Update already running.")
    24. End If
    25. Else
    26. If SilentMode = False Then MsgBox("Das Update konnte nicht gestartet werden, da der angegebene SteamCMD-Pfad nicht korrekt ist." & vbCrLf & vbCrLf & "Bitte Einstellungen überprüfen!", vbExclamation, "Fehler beim Starten des Updates")
    27. ServerLogFiles(srvIdx).Add("SERVER STATUS", "UpdateGameServer(): Update could not be started - Wrong SteamCMD path.")
    28. End If
    29. ServerLogFiles(srvIdx).Save()
    30. Return procID
    31. End Function


    Kommunikation mit dem Web-Interface:

    VB.NET-Quellcode

    1. ' --------------------------------------------------------------------------------------------------------------------
    2. ' --------- TCP VERBINDUNG -------------------------------------------------------------------------------------------
    3. ' --------------------------------------------------------------------------------------------------------------------
    4. Sub ValidateRequestAndRespond(sender As TCPControl, ByVal request As String)
    5. If TCPServer.ServerStatus = True Then
    6. Dim command As String = ""
    7. Dim parameter As String = ""
    8. Dim SrvIdx As Integer = -1
    9. Dim GameServerStatus As Boolean = False
    10. If request.IndexOf("(") > -1 And request.IndexOf(")") > request.IndexOf("(") Then
    11. command = request.Substring(0, request.IndexOf("("))
    12. parameter = request.Substring(request.IndexOf("(") + 1, request.IndexOf(")") - (request.IndexOf("(") + 1))
    13. Else
    14. command = request
    15. parameter = ""
    16. End If
    17. Select Case command
    18. Case "SetServerUpdate"
    19. SrvIdx = ProgramData.GetSrvIdx(CInt(parameter))
    20. If IsNumeric(parameter) And SrvIdx > CONST_INVALID Then
    21. GameServerStatus = CBool(UpdateGameServer(CInt(parameter), True))
    22. If GameServerStatus = True Then
    23. SetServerControls(ServerStatus.Updating, CInt(parameter))
    24. TCPServer.SendToClient("success")
    25. Else
    26. TCPServer.SendToClient("failed")
    27. End If
    28. UpdateText(TaReceive, request)
    29. ServerLogFiles(SrvIdx).Add("REMOTE CONTROL", "SetServerUpdate received")
    30. ServerLogFiles(SrvIdx).Save()
    31. Else
    32. TCPServer.SendToClient("wrong ID")
    33. End If
    34. ' ...
    35. Case Else
    36. 'Me.TaReceive.Text = request
    37. UpdateText(TaReceive, "WRONG CMD: " & request)
    38. AppLogFile.Add("REMOTE CONTROL", request & " received (wrong command)")
    39. AppLogFile.Save()
    40. TCPServer.SendToClient("wrong command")
    41. End Select
    42. End If
    43. End Sub
    44. ' UPDATE TEXTBOX
    45. Private Sub UpdateText(RTB As RichTextBox, txt As String)
    46. If RTB.InvokeRequired Then
    47. RTB.Invoke(New UpdateTextDelegate(AddressOf UpdateText), New Object() {RTB, txt})
    48. Else
    49. If txt IsNot Nothing Then RTB.AppendText("[" & Format(Now, "dd.MM.yyyy HH:mm:ss") & "] " & txt & vbCrLf)
    50. RTB.ScrollToCaret()
    51. End If
    52. End Sub

    Igel schrieb:

    Kann ein Timer standardmäßig nicht threadübergreifend gestartet werden?
    Was meinst du mit "threadübergreifend gestartet"?
    Den Timer aus einem NebenThread starten (also die 2. Frage)?
    Das geht mit einem Winforms-Timer nicht, weil der WinForms-Timer ist ein Gui-Element, und alle Gui-Elemente dürfen nur vom Gui-Thread angesprochen werden.

    Aber es gibt noch andere Timer.
    Die kann man durchaus von verschiedenen Threads aus starten/stoppen, allerdings muss man dazu einen SyncLock setzen (Threading ist nunmal so).

    Igel schrieb:

    Wie bekomme ich das hin, dass der Timer auch aus dem anderen Thread heraus gestartet wird?
    Für einen Winforms-Timer verwendet man Control.BeginInvoke, für einen Timers.Timer oder Thread.Timer einen SyncLock-Block.


    Zu deim Code kannichnix nuir allgemein sagen, sieht verworren aus, und ich weiss nicht, welche Methode im Client läuft, und welche im Server - ich weiss nichtmal, ob du unter Client/Server das gleiche verstehst wie ich.
    Danke für deine Antwort ^^

    Der ServerManager ist der Server, der Client ist komplett in php programmiert, also ist alles, was VB ist der Server ;)

    Ich hab das jetzt einfach so gelöst, dass der Timer immer läuft und nur wenn die notwendigen Bedingungen bestehen (Status auf update, aber Prozess schon beendet), was im Timer ausgeführt wird. Das klappt nun, egal von wo die Funktion aufgerufen wird. :thumbup:

    cya Igel
    (hmm - scheints verstehen wir unter Client tatsächlich was anneres.
    Weil meines Wissens kann man mit PHP keinen Client programmieren.)

    Aber ansonsten scheint mir deine Lösung höchst sinnvoll: Der Timer tickt kontinuirlich, aber Jobs werden nur ausgeführt, wenn bestimmte Bedingungen dafür vorliegen.