TThread: mehrere Verzeichnisse gleichzeitig beobachten



  • Hallo!

    Ich habe mich mit dem Thema Threads auseinandergesetzt und das Beispiel aus folgender Seite nachprogrammiert.
    http://bcb-tutorial.c-plusplus.net/Thread/artikel7.html

    Nun möchte ich aber, mehrere Verzeichnisse gleichzeitig beobachten. Dazu hatte ich das von der oben genannten Seite übernommene Beispiel folgendermassen verändert:
    - Die zu beobachtenden Verzeichnisse lese ich aus einer INI-Datei und schreibe sie in eine ValueList
    - Der Code (Veränderung) sieht folgendermassen aus:

    TWatchThread
    .h:

    int iWatchPath; 
    __fastcall TWatchThread(int i); //Konstruktor für die Anzahl der Verzeichnisse
    

    .cpp:

    __fastcall TWatchThread::TWatchThread(int i) : TThread(false)
    {
        iWatchPath = i;
        FreeOnTerminate = true; //Aufruf von TThread::Terminate() gibt Thread frei.
    }
    
    void __fastcall TWatchThread::Execute()
    {
      for(iWatchPath; iWatchPath>=1; iWatchPath--)
      {
        FChangeHandle = FindFirstChangeNotification(Form1->vl1->Keys[iWatchPath].c_str(), 
    false, FILE_NOTIFY_CHANGE_FILE_NAME);
        if (FChangeHandle != INVALID_HANDLE_VALUE)
        {
            while (true)
            {
                if (WaitForSingleObject(FChangeHandle, 500) == WAIT_OBJECT_0) //Änderung.
                    Synchronize(TuWas); 
    
                FindNextChangeNotification(FChangeHandle);
    
                iWatchPath--;
                if(iWatchPath==0)
                  iWatchPath = Form1->iAnzahl;
                Execute();
    
                if (Terminated)
                    return; //Raus.
            }//while
        }//if
      }//for
    }
    

    TForm1:

    int iAnzahl; //Anzahl der Verzeichnisse
    
    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
      if (FWatchThread != NULL)
        FWatchThread->Terminate(); 
      FWatchThread = new TWatchThread(iAnzahl);   
    }
    

    Wie ihr seht habe ich in der Execute()-Methode eine for-Schleife reingebaut, damit ich mehrere Verzeichnisse beobachten kann, was auch funktioniert. Nachteil ist, ich kann die Verzeichnisse nicht gleichzeitig beobachten!

    Nun genug geredet 😉 und zu meiner Frage:
    Wie kann ich sonst noch mehrere Verzeichnisse gleichzeitig beobachten?
    Wie würdet ihr das machen?

    Danke!


  • Mod

    Hallo

    wie waere es mit mehreren THreads

    MfG
    Klaus



  • @KlausB
    erst einmal herzlichen Glückwunsch nachträglich zur Beförderung!

    Für jedes Verzeichnis ein Thread? Daran hatte ich auch gedacht. Problem: Anzahl der Verzeichnisse ist erst zur Laufzeit bekannt und ich brauche so ein Denkstoß, wie ich Threads zur Laufzeit erzeugen kann.

    Oder gibt es da vielleicht noch eine andere Idee?

    Danke!



  • Du erstellst ihn bereits zur Laufzeit:

    FWatchThread = new TWatchThread(iAnzahl);
    


  • PAB schrieb:

    Du erstellst ihn bereits zur Laufzeit:

    FWatchThread = new TWatchThread(iAnzahl);
    

    😃
    Ich habe im Moment solchen Knoten im Kopf 🙂 😃 😉

    Hier erzeuge ich "nur" einen Thread für alle Verzeichnisse.
    Wie kann ich für jedes Verzeichnis (Anzahl der Verzeichnisse variabel) einen Thread erzeugen, ohne daß sie sich in der Execute()-Methode in die Quere kommen?

    Oder habe ich da vielleicht ein Verständnisproblem?

    Danke!



  • Dafür nimmste am besten den Originalcode ausm BCB-Tutorial. Der ist bereits so ausgelegt, dass er ne Pfadangabe erhalten will.
    Im Programm selber kannst du dann die INI auslesen und in ner Schleife die benötigte Anzahl an Threads erzeugen (freigeben nicht vergessen).

    Allerdings greift der Thread auf ein Element der Oberfläche (FilesFileListBox) zu, das musst du ihm noch abgewöhnen. Vielleicht bietet sich als Ersatz eine TStringList pro Thread an, aus der du dann die Daten ausliest.

    Solange sich die Threads nicht um eine gemeinsame Variable oder Objekt prügeln müssen, laufen die friedlich nebeneinander her.



  • Danke @PAB, aber das Problem ist immer noch, daß die Verzeichnisse auch in diesem Fall nicht gleichzeitig überwacht werden, sondern wieder eins nach dem anderen.

    ➡ Wenn ich ein Verzeichnis überwache, dann möchte ich dies permanent tun. Dafür nehme ich INFINITE:

    if(WaitForSingleObject(hChangeHandle, /*500*/ INFINITE) == WAIT_OBJECT_0)
      TuWas();
    

    Was bei nur ein einzelnes Verzeichnis auch prima klappt. Wenn Änderung auftritt, dann führe ich meine Funktion aus, und warte danach wieder auf die nächste Änderung.

    Nun will ich aber mehrere Verzeichnisse gleichzeitig und permanent überwachen, und nicht nur alle 500 MS. Wenn ich dies wie oben beschrieben tue, dann geht es nicht, weil damit nur das erste Verzeichnis überwacht wird, denn es wird immer wieder auf die nächste Änderung dort gewartet (wegen INFINITE). Das heißt, es geht nicht mehr weiter, und die übrigen Verzeichnisse bleiben unüberwacht.

    Eine "halbe" Lösung ist ja für INFINITE eine bestimmte Zeit zu nehmen, z.B. 500 MS. Damit überwache ich jedes Verzeichnis nur 500 MS, und wenn innerhalb dieser Zeit eine Änderung im überwachten Verzeichnis auftritt, dann kriege ich sie auch mit.

    Problem: Falls sich in einem Verzeichnis innerhalb der Zeit, in der dieses Verzeichnis unüberwacht ist, was ändert, dann kriege ich das nicht mit, und kann nicht reagieren!
    (Bei z.B. 11 Verzeichnissen und 500 MS Wartezeit bleibt ein Verzeichnis 5 Sekunden unüberwacht.)

    Ich hoffe, ich habe das Problem näher erklären können, und kein M*** erzählt.

    Hat jemand eine Idee, wie ich mehrere Verzeichnisse permanent und gleichzeitig überwachen kann?
    Wie gesagt, ich brauche nur so einen Denkanstoß oder einen kleinen Hinweis.

    Danke!



  • WaitForMultipleObjects dürfte wohl in diesem Fall dein Freund sein?
    Ich würde übrigens trotzdem den Timeout vielleicht auf 200ms stellen, damit du zwischendurch im Thread auch auf Terminate prüfen kannst (o;

    -junix



  • Oh, ich hab grad nochmals nachgeschaut... die Lösung deines Problems führt wohl zwangsläufig über die Funktion [msdn]ReadDirectoryChangesW[/msdn], im Overlapped-Modus betrieben und oben von mir genannter Funktion...

    EDIT: Und wo wir grad dabei sind: Spar dir doch die zusätzliche if und verwende einfach die implizite if die dir "while" mit dem Abbruchkriterium bietet?

    -junix



  • Das hört sich sehr gut und viel versprechend an.
    Ich werde es gleich ausprobieren und melde mich dann.

    herzlichen Dank @junix!



  • Hallo!

    Hier und da was gelesen und bißchen Code "geklaut" 😃 😉 half mir, mein Programm "umzukrempeln". Nun kann ich auch mit "ReadDirectoryChangesW" erstmal ein (einzelnes) Verzeichnis überwachen und die Veränderungen dort abfangen.

    Für diejenigen, die sich dafür interessieren, Code folgt (vielleicht kann ihn jemand von den Profis optimieren und für die anderen, die so etwas suchen, zur Verfügung stellen)

    HANDLE FDirectoryHandle;
    TWatchThread *FWatchThread;
    //HANDLE FCompletionPort;
    DWORD FBytesWritten, FNotifyFilter;
    BYTE FNotificationBuffer[4];
    DWORD numBytes;
    TOverlapped FOverlapped;
    POverlapped FPOverlapped;
    
    void __fastcall TForm1::FormCreate(TObject *Sender)
    {
      //FCompletionPort = 0;
      FDirectoryHandle = 0;
      FPOverlapped = &FOverlapped;
      ZeroMemory(&FOverlapped, sizeof(FOverlapped));
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::FormDestroy(TObject *Sender)
    {
      btnStopClick(Sender);        
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::btnStopClick(TObject *Sender)
    {
      /*if (FCompletionPort == 0)
        return;
      PostQueuedCompletionStatus(FCompletionPort, 0, 0, NULL);
      FWatchThread->WaitFor();
      FWatchThread->Free(); */
      CloseHandle(FDirectoryHandle);
      FDirectoryHandle = 0;
      //CloseHandle(FCompletionPort);
      //FCompletionPort = 0;
      edPath->Enabled = true;
      btnStart->Enabled = true;
      btnStop->Enabled = false;
    
    }
    //---------------------------------------------------------------------------
    void __fastcall TForm1::btnStartClick(TObject *Sender)
    {
      lbEvents->Clear();
    
      FNotifyFilter = FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME
                      | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE
                      | FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_LAST_ACCESS
                      | FILE_NOTIFY_CHANGE_CREATION | FILE_NOTIFY_CHANGE_SECURITY;
    
      FDirectoryHandle = CreateFile(edPath->Text.c_str(),
                                    FILE_LIST_DIRECTORY, FILE_SHARE_READ |
                                    FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                    NULL,
                                    OPEN_EXISTING,
                                    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
                                    0);
      if (FDirectoryHandle == INVALID_HANDLE_VALUE)
      {
        Beep();
        FDirectoryHandle = 0;
        ShowMessage(SysErrorMessage(GetLastError()));
        return;
      }
      //FCompletionPort = CreateIoCompletionPort(FDirectoryHandle, 0, (long int)(this), 0);
      ZeroMemory(FNotificationBuffer, sizeof(FNotificationBuffer));
      FBytesWritten = 0;
      if (!ReadDirectoryChangesW(FDirectoryHandle, FNotificationBuffer,
                                 sizeof(FNotificationBuffer), true, FNotifyFilter,
                                 &FBytesWritten, &FOverlapped, NULL))
      {
        CloseHandle(FDirectoryHandle);
        FDirectoryHandle = 0;
        //CloseHandle(FCompletionPort);
        //FCompletionPort = 0;
        ShowMessage(SysErrorMessage(GetLastError()));
        return;
      }
    
      edPath->Enabled = false;
      btnStart->Enabled = false;
      btnStop->Enabled = true;
      FWatchThread = new TWatchThread(this);
      TWatchThread(FWatchThread).Resume();
    }
    //---------------------------------------------------------------------------
    __fastcall TWatchThread::TWatchThread(bool CreateSuspended) : TThread(CreateSuspended)
    {
      FreeOnTerminate = true; //Aufruf von TThread::Terminate() gibt Thread frei.
    }
    //---------------------------------------------------------------------------
    _fastcall TWatchThread::~TWatchThread()
    {
      TThread::inherited(); //Standarddestruktor.
    }
    //---------------------------------------------------------------------------
    void __fastcall TWatchThread::Execute()
    {
      while (!Terminated)
      {
        /*GetQueuedCompletionStatus(FCompletionPort, &numBytes, &CompletionKey, &FPOverlapped, INFINITE);
        if (CompletionKey != 0 )*/
        if(WaitForSingleObject(FDirectoryHandle, INFINITE) == WAIT_OBJECT_0)
        //if(WaitForMultipleObjects(iAnzahl, FDirectoryHandle, true, INFINITE) == WAIT_OBJECT_0)
        {
          Synchronize(Events);
    
          FBytesWritten = 0;
          ZeroMemory(FNotificationBuffer, sizeof(FNotificationBuffer));
          ReadDirectoryChangesW(FDirectoryHandle, FNotificationBuffer, sizeof(FNotificationBuffer),
          true, FNotifyFilter, &FBytesWritten, &FOverlapped, NULL);
        }
        else
          Terminate();
      }
    }
    //---------------------------------------------------------------------------
    void __fastcall TWatchThread::Events()
    {
      Form1->lbEvents->Items->Add(Now());
    }
    //---------------------------------------------------------------------------
    

    Mein Problem besteht aber dennoch darin, mehrere Verzeichnisse gleichzeitig und permanent zu überwachen. Mit "WaitForMultipleObjects" sollte das eigentlich gehen, aber ich habe es bis jetzt ehrlich gesagt nicht geschafft.

    Wie soll ich da vorgehen?
    Kann mir da vielleicht jemand helfen?

    Danke!


Anmelden zum Antworten