Einzelinstanzanwendung funktioniert nicht wie ich möchte

  • VB.NET

Es gibt 19 Antworten in diesem Thema. Der letzte Beitrag () ist von HerrFrie.

    Einzelinstanzanwendung funktioniert nicht wie ich möchte

    Hallo zusammen,

    ich habe versucht eine Lösung im Forum oder bei Google zu finden, bin aber bis jetzt gescheitert.
    Ich habe eine Anwendung, die auf einem Rechner liegt und im Netzwerk freigegeben ist.

    Jetzt hätte ich es gerne so, dass das Programm nur insgesamt 1 x geöffnet werden kann.

    1. User Nr. 1 öffnet das Programm von Rechner 1 --> so ist gut, darfa
    1.1 User Nr. 1 öffnet erneut das Preogramm von Rechner 1 --> Fehlermeldung durch Haken für Einzelinstanzanwendung, funktioniert so wie man sich das denkt :D

    2. User Nr. 1 öffnet das Programm von Rechner 1 --> so ist gut, darfa
    2.1 User Nr. 2 öffnet das Programm von Rechner 2 --> jetzt SOLL auch eine Fehlermeldung kommen, dass das Programm bereits in Benutzung ist, funktioniert allerdings NICHT mit dem Haken für Einzelinstanzanwendung ;(

    Das Event "StartupNextInstance" scheint mir nur auf dem lokalen Rechner nachzuschauen, ob die Anwendung bereits geöffnet ist. Gibt es auch eine einfache Möglichkeit das für Netzwerkzugriff zu erreichen ?
    Eine extra Datei, die beim ersten Öffnen erstellt wird und diese Info enthält wäre keine gute Lösung, da im Falle eines Stromausfalls die Datei nachher immer noch da wäre :S . Leider kommt so ein Stromausfall bei uns schon fast regelmäßig vor.

    Gruß
    HerrFrie
    du musst dein Programm überprüfen lassen, ob es schon läuft, dafür muss das Programm mit sich selbst kommunizieren.
    Eine Idee wäre, du baust eine Verbindung über TCP oder UDP auf und fragst somit nach, ob dein Programm auf einem zuvor ausgewählten Port hört, tut es das sendet es eine Antwort. Wenn nicht, wird wohl dein Programm noch nicht laufen.
    Ich wollte auch mal ne total überflüssige Signatur:
    ---Leer---
    Hi,

    die Lock-Datei ist schon der richtige Ansatz. Um den Stromausfall ermitteln zu können, kann man in die Datei periodisch einen Zeitstempel (Datum+Uhrzeit) schreiben, z.B. jede Stunde (je nachdem, wie lange das Programm voraussichtlich läuft). Bei einem Stromausfall wird die Datei nicht mehr aktualisiert, also wird beim nächsten Programmstart die Zeit in der Datei mit der aktuellen Zeit verglichen, sofern sie existiert. Ist das Intervall größer als der Aktualisierungszeitraum, liegt ein Stromausfall vor, ansonsten läuft das Programm auf einem anderen Rechner.
    Das Aktualisierungsintervall muss auf Nutzungshäufigkeit und -dauer des Programms abgestimmt werden. Wird es z.B. nur 1x täglich 8 Stunden gestartet, reicht ein Intervall von 4 Stunden. Wird es 10x täglich für 10 Minuten genutzt, muss das Intervall der kürzesten Nutzungspause entsprechen.
    Und nicht vergessen: Die Datei sollte beim regulären Beenden des Programms gelöscht werden.

    Viel komplexer ist diese Idee: Erstelle auf dem Rechner, der das Programm startet, ein Remotingobjekt (Singleton), welches eine Eigenschaft "IsRunning" veröffentlicht. Trage in die Lock-Datei die Netzwerkadresse des Objekts auf dem Rechner ein, auf dem das Programm läuft. Ein anderer Programmstart verbindet sich zu der in der Datei hinterlegten Adresse und bekommt "True" oder "keine Verbindung möglich" zurück. Fall1: Programm läuft, Fall 2: Stromausfall, Datei aktualisieren. Fall 3 (keine Datei vorhanden) alles ok.
    Gruß
    hal2000

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

    Danke euch schon einmal für die Hilfe. Jetzt muss ich mal schauen, was bei uns am Besten passt.

    Es handelt sich um ein größeres Unternehmen, wo zich Programme zum Schutz gegen alles Mögliche laufen. Um da irgendwelche Rechte oder Ports zu handeln, werden mir wahrscheinlich die Rechte fehlen.

    Da das Programm im "Normal-User-Betrieb" eine maximale Öffnungszeit von 5 Minuten eingestellt hat, bevor es sich automatisch schließt, werde ich es wohl doch mit einer Datei und einem Zeitstempel probieren.
    Lediglich der "Admin-Betrieb" hat keine maximale Benutzerzeit vorgegeben. Mal sehen ob das irgendwie klappt.

    Gruß
    HerrFrie
    Was mich interessieren würde, warum gibt es in einem größeren Unternehmen öfter Stromausfälle? Ist das Stromnetz so schwach?

    Ansonsten würde ich persönlich das ganze auch über den zweiten vorschlag von Hal machen.
    lg.

    LucaWelker
    Warum nicht lock-Datei und Kommunikation kombinieren.
    Beim starten: Schreiben einer entsprechenden Datei auf ner Netzwerkfreigabe. INHALT ist die IP des Rechners, der das Prog gerade gestartet hat. Beim schließen, löschen der Datei.

    Wenn ein zweites Programm startet, prüft es ob die Datei existiert.
    Wenn nein -> trivial, s.o.
    Wenn ja -> Schicken einer Anfrage über UDP. Kommt eine Antwort innerhalb von x Sekunden -> beenden. Ansonsten Datei neu mit eigener IP.

    Hätte den Vorteil, dass UDP extrem trivial ist und man es so einfach und schnell hinbekäme.
    Wir scheinen mit unserer Stromanbindung recht ungünstig zu liegen, da es bei Gewittern shr oft vorkommt, dass in der Umgebung der Strom weg ist. Dazu kommt noch, dass bei Bauarbeiten meist immer exakt eine Leitung getroffen wird, die auch uns versorgt. Im Schnitt kann man sagen, dass wir das Problem 1-2 mal im Monat haben, nur die Ausfalldauer ist unterschiedlich. Manchmal ist auch nur eine Phase betroffen, da kann man Glück haben, dass nur das andere ausfällt.

    @Picoflop, wie genau kann ich das mit UDP machen. Da habe ich bis jetzt keine Ahnung von, weil noch nicht benötigt. Ich müsste das erst mal testen, wie sich das hier im Netzwerk verhält.
    Wenn ich das richtig verstehe wird geschaut, ob eine Verbindung zu der IP, also des Rechners besteht. Dies wird aber doch im Normalfall bei uns so sein, da sämtliche PCs Montagmorgen eingeschaltet werden und dann bis Ende der Woche 24h laufen. Oder verstehe ich das falsch ?

    Gruß
    HerrFrie

    HerrFrie schrieb:

    Oder verstehe ich das falsch ?

    Ja ... ;)

    Im PRINZIP kannst du einfach das hier nehmen:
    [VB 2008] UDP Chat im lokalen Netz

    1 - Programm startet.
    2 - Horchen einschalten
    3 - Schicke "Hallo?" an alle (Broadcast halt, dann ist dir die IP eben auch schnuppe)
    4 - Es kommt "Ich!" zurück -> Programm beenden
    5 - Es kommt innerhalb von x Sekunden keine Antwort -> Wir sind Nummero Uno!
    .
    .
    .
    81 - Auf dem immer noch lauschenden Port kommt "Hallo?" rein. Wir antworten "Ich!" - damit bleiben WIR Cheffe!
    Habe das Prg getestet und es funktioniert soweit. Die erstellte Exe wurde auf das Netzlaufwerk kopiert und von diversen Rechnern gestartet, zum allgemeinen Chat.

    Jetzt probiere ich gerade weiter, wie ich das richtig einbette.

    Ich habe im Event MyApplication_Startup geschrieben

    VB.NET-Quellcode

    1. Private Sub MyApplication_Startup(ByVal sender As Object, ByVal e As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) Handles Me.Startup
    2. Dim ipAddress As Net.IPHostEntry = Net.Dns.GetHostEntry(Net.Dns.GetHostName)
    3. Dim IP As String = ipAddress.AddressList.GetValue(0).ToString
    4. c.Listen()
    5. c.SendStr(IP)
    6. Threading.Thread.Sleep(5000)
    7. End Sub


    Damit wird dann die IP des aktuellen Rechners an den Port 12345 gesendet. Denke ich :S .
    Der Threading.Sleep wird aber mit Sicherheit nicht richtig sein, weil dadurch doch alles angehalten wird, oder ?
    Egal, erst mal weiter erklären.
    In der gleichen Class steht dann

    VB.NET-Quellcode

    1. Private Sub c_GotMessage(ByVal m As String, ByVal rep As Net.IPEndPoint) Handles c.GotMessage
    2. If m = "Anfrage abgelehnt" Then
    3. MessageBox.Show("Das Programm ist bereits geöffnet! ", "Programm schon geöffnet", MessageBoxButtons.OK, MessageBoxIcon.Information, MessageBoxDefaultButton.Button1)
    4. c.Stop()
    5. End
    6. End If
    7. End Sub

    Das Sleep soll halt ein paar Sekunden auf eine Antwort warten und in der Zeit auf das Event c.GotMessage warten und reagieren.

    Das wäre dann die Option beim Starten des Programms.
    Wenn das Programm bereits auf einem Rechner gestartet ist, ist das

    VB.NET-Quellcode

    1. c.Listen()

    noch aktiv, sodass der Port 12345 weiterhin beobachtet wird.
    Auch hier im Hauptform gibt es das Event für

    VB.NET-Quellcode

    1. c.GotMessage

    Hier wird dann überprüft, ob die empfangene IP der, des Rechners entspricht, auf dem das Programm bereits ausgeführt wird.

    VB.NET-Quellcode

    1. Private Sub c_GotMessage(ByVal m As String, ByVal rep As Net.IPEndPoint) Handles c.GotMessage
    2. Dim ipAddress As Net.IPHostEntry = Net.Dns.GetHostEntry(Net.Dns.GetHostName)
    3. Dim IP As String = ipAddress.AddressList.GetValue(0).ToString
    4. If m <> IP and m <> "Anfrage abgelehnt" Then
    5. c.SendStr("Anfrage abgelehnt")
    6. End If
    7. End Sub

    Ist dies nicht der fall, wird "Anfrage abgelehnt" gesendet, was dann von dem PC wo das Programm gestartet wird empfangen werden soll.

    Sollte das so funktionieren ? Mit dem Sleep funzt es so jedenfalls noch nicht, ich bräuchte also noch etwas Hilfe :D .

    Gruß
    HerrFrie
    1. Ich würde das nicht im Startp der Anwendung machen. Stattdessen würde ich eine Splashform vorschalten, die das prüfen übernimmt und die dann entweder auf die Hauptform umschaltet.
    2. Statt zu sleep'en kannst du im einfachsten Fall einen 5 Sekunden timer nehmen.
    Ich habe nun alles aus dem Startup entfernt.
    Ein SplashScreen wurde erstellt und als Begrüßungsbildschirm ausgewählt. Die Startform ist mdi_Main.

    Sobald ich allerdings irgendetwas in den Splash schreibe, wird dieser gar nicht mehr angezeigt und das mdi_Main wird angezeigt.
    Lasse ich die Grunddaten des erstellten Splash stehen, wird der Begrüßungsbildschirm angezeigt und verschwindet nicht. Es wird allerdings auch das mdi_Main gar nicht ausgeführt.

    Wie bekomme ich das denn ordentlich hin ?

    Der Ablauf sieht im Moment so aus.
    mdi_Main ist Startform. Aus ihr wird im Load Event die Datenbank eingeladen und im Anschluß das frm_Login aufgerufen. Nach erfolgreichem Login wird dann im mdi_Main das frm_Artikelsuche angezeigt.

    So soll es dann werden:
    Das Programm startet und es wird eine Abfrage per UDP gesendet (IP Adresse des Rechners). Ein eventuell geöffnetes Programm schaut, ob es sich um die eigene IP handelt. Wenn ja wird nichts gemacht, wenn nein, wird ein String zurück geschickt.
    Das Programm, welches gestartet wurde wartet eine Zeit von 3 Sekunden, ob es eine Antwort bekommt. Ist dies der Fall schließt es sich, ansonsten wird auf das mdi_Main aufgerufen.

    Aber irgendwie klappt das nicht so.
    Ich lese hier (neuerdings!) solche Dinge wie "Artikelsuche" und "wir wollen". Es handelt sich also offenbar um eine Bastellösung für einen Betrieb. Ihr habt eine Anwendung, die auf eine Datenbank lädt, also entweder eine missbrauchte Excel-Datei oder eine nicht netzwerkfähige Access-Datenbank. Da Mehrfachzugriffe kollidieren, versuchst du, die Datenbank davor zu schützen, indem du den Mehrfachstart der zugreifenden Anwendung verhinderst. Diese Lösung vergrößert die Wartezeiten der Nutzer auf die Datenbank und geht garantiert früher oder später in die Hose.

    Da der Rechner, der die Netzwerkfreigabe mit der Anwendung anbietet, laufen muss, wenn die Anwendung benötigt wird, könnte man darauf einen MySQL-Server (oder MS SQL Express) installieren und die Datenbank dort hosten. Die Anwendung greift dann direkt auf den SQL-Server zu, welcher von sich aus mit mehreren Benutzern gleichzeitig umgehen kann.
    Gruß
    hal2000
    @hal2000,

    fast. Wir benutzen zurzeit verschiedene Excel Listen, um unsere Artikel mehr oder weniger zu verwalten.

    Deshalb versuche ich das gerade von mehreren Listen auf eine Anwendung umzustricken. Eine Datenbank hat es somit vorher nicht gegeben und ich bekomme dort auch keine Möglichkeit eine MySQL Datenbank zu erstellen, weshalb ich das jetzt mit einer Datenbankdatei von Access mache. Access ist aber auch nicht auf jedem Rechner verfügbar, sonst hätte ich das direkt in Access kreieren können.
    Große Firmen --> große Wasserköpfe ....

    Da der Zugriff auf die Anwendung von jedem x-beliebigen Rechner funktionieren soll, liegt die Anwendung auf einem Netzerklaufwerk. Die Anwendung soll aber nur einmal gleichzeitig geöffnet werden können, um keinen Mist mit der DB zu veranstallten.

    Gruß
    HerrFrie
    Ok, der Umstand, auf dem Server nichts weiter zu dürfen als Dateien zu lagern, schränkt die Möglichkeiten natürlich ein. Ich würde das Remoting-Modell fahren (statt die Socket-Methode, weil einfacher). Hier ein Beispiel:

    Netzwerkordner:
    - Anwendung
    - Lock-Datei

    Anwendung (mit einer WinForms-Anwendung simuliert):

    VB.NET-Quellcode

    1. Class DBApplication
    2. Inherits MarshalByRefObject
    3. Public ReadOnly Property IsRunning() As Boolean
    4. Get
    5. Return True
    6. End Get
    7. End Property
    8. End Class


    VB.NET-Quellcode

    1. Imports System.Runtime.Remoting
    2. Imports System.Runtime.Remoting.Channels
    3. Imports System.Runtime.Remoting.Channels.Tcp
    4. Dim tcpc As TcpChannel
    5. Private Sub cmdRun_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdRun.Click
    6. 'Lock-Datei mit URL zu dieser Instanz schreiben (ausgeführt beim Start jeder Instanz)
    7. tcpc = New TcpChannel(5555)
    8. Channels.ChannelServices.RegisterChannel(tcpc, False) 'Eventuell einen ChannelNamen vergeben
    9. RemotingConfiguration.RegisterWellKnownServiceType(GetType(DBApplication), "DBApp", WellKnownObjectMode.SingleCall)
    10. End Sub
    11. Private Sub cmdCheck_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCheck.Click
    12. 'Lock-Datei auslesen (beim Start)
    13. Dim dba As DBApplication = CType(Activator.GetObject(GetType(DBApplication), URL_AUS_DATEI), DBApplication) 'URL-Beispiel: "tcp://localhost:5555/DBApp"
    14. MessageBox.Show("IsRunning: " & dba.IsRunning.ToString())
    15. 'Instanz beenden, falls eine Antwort kommt, sonst Datei aktualisieren und nicht beenden ("Stromausfall")
    16. End Sub
    17. Private Sub cmdStop_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdStop.Click
    18. ChannelServices.UnregisterChannel(tcpc)
    19. End Sub
    Gruß
    hal2000
    Hallo Hal2000,

    ich habe dein Beispiel mal erstellt. Remote hört sich für mich jetzt erst einmal kritisch an, weil unser Virenscanner ziemlich pingelig eingestellt ist, aber mal sehen.

    Kannst du mir vielleicht ein paar mehr Beschreibungen geben ? Wenn ich das jetzt richtig verstanden habe, schiebe ich meine Exe in meinen Netzordner. Zusätzlich soll dann eine Lock Datei "DBApp" beim Programmstart erstellt werden ?
    Habe das erst mal ohne eine zusätzliche Datei gemacht und den Run Button gedrückt.
    Danach auf Check und er meldet mir True.

    Wie genau müste ich da weiter vorgehen ? Mit einer Datei im Stromausfall wüsste ich aber doch nicht, dass in Wirklichkeit niemand mehr das Programm geöffnet hat ?

    Gruß
    HerrFrie
    Hi,

    hier nochmal die lange Version:

    Das Programm liegt auf dem Netzlaufwerk. Beim Start wird (ebenfalls auf dem Netzlaufwerk - einfach im gleichen Ordner) geprüft, ob die Datei MyProgram.lock existiert. (Fall 1) Ist das nicht der Fall, sind wir die erste Instanz, die gestartet wurde. Wir legen die Datei also an, und zwar mit folgendem Inhalt:
    - Zeitstempel (Datum+Zeit)
    - Eigene Remoting-Adresse, z.B. "tcp://123.45.67.89:5555/DBApp"

    Außerdem starten wir einen Remote-Endpunkt (der ausführende Rechner), der Anfragen entgegennimmt.

    Startet nun ein zweiter Benutzer das Programm, gibt es zwei Möglichkeiten - in beiden Fällen existiert die Datei bereits:
    - Der erste Benutzer ist noch aktiv und hat einen aktiven Remoteendpunkt.
    - Der erste Benutzer ist abgestürzt (Stromausfall) und hat demnach keinen aktiven Remoteendpunkt.

    Das startende Programm prüft also wieder auf die Existenz der Datei (Fall 2 von oben). Diese ist vorhanden, also wird sie ausgelesen. Anhand des Zeitstempels kann man überprüfen, wie lange der Benutzer bereits aktiv ist (und ihm eventuell eine Mail schreiben, er soll doch bitte fertig werden). In jedem Fall versucht das Programm aber, eine Verbindung zum Remoteendpunkt aufzubauen. Bei Erfolg ist der erste Nutzer noch aktiv - wir dürfen die Datenbank nicht sperren. Bei Misserfolg ist der erste Benutzer abgestürzt, also lösche die Datei und gehe zu Fall 1.

    Wenn die Remoting-Sache wegen einer Firewall auf den einzelnen Rechnern nicht funktioniert, musst du dich auf den Zeitstempel verlassen. Aber auch dafür gibt es eine Lösung: aktualisiere den Zeitstempel in der Datei periodisch (z.B. jede Minute). Eine zweite Instanz prüft, ob die Datei existiert UND ob die aktuelle Zeit bereits weiter fortgeschritten ist als der Zeitstempel + Aktualisierungsintervall. Wenn die zeit abgelaufen ist, existiert die Datei unberechtigt und kann überschrieben werden. Bei diesem Verfahren kann es aber zu Problemen kommen, da die Rechner sich nicht gegenseitig verifizieren (Google: Racebedingung).
    Gruß
    hal2000

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

    Hmm,

    bin da grad am Probieren, aber irgendwie will das noch nicht so.
    Benutze deinen Code. Beim cmdRun lasse ich eine Datei schreiben mit IP und Uhrzeit. Beim Testen habe ich da schon mal Probleme, mir die eigene IP anzeigen zu lassen.Bei Win7 zeigt er mir beim Auslesen nicht die IP an.

    VB.NET-Quellcode

    1. Dim ipAddress As Net.IPHostEntry = Net.Dns.GetHostEntry(Net.Dns.GetHostName)
    2. Dim IP As String = ipAddress.AddressList.GetValue(0).ToString

    Bei XP funzt es mit der IP, bei Win7 muss ich .GetValue(3) eingeben. Das funzt dann aber nicht bei XP :(

    Wenn ich cmdCheck drücke schreibt er mir immer True. Ob die Datei da ist oder nicht interessiert nicht. ?(
    Ok,

    das mit der IP habe ich jetzt soweit hinbekommen. Auch die Lösung per UDP scheint bei mir zu funktionieren und wird auch nicht im Firmen-Netz als Bedrohung angemeckert. Ich habe das jetzt so erweitert, dass dem 1. Besitzer kurz eingeblendet wird, das Benutzer X versucht, das Programm zu öffnen. Der Benutzer, der das Programm ein 2tes mal öffnen will wird angzeigt, dass der Benutzer Y das Programm bereits verwendet.

    Möchte aber trotzdem auch versuchen, dass das mit der Lock Datei funktioniert, da hänge ich allerdings irgendwie.