ABOUT Visual Basic Programmieren Programmierung Download Downloads Tips & Tricks Tipps & Tricks Know-How Praxis VB VBA Visual Basic for Applications VBS VBScript Scripting Windows ActiveX COM OLE API ComputerPC Microsoft Office Microsoft Office 97 Office 2000 Access Word Winword Excel Outlook Addins ASP Active Server Pages COMAddIns ActiveX-Controls OCX UserControl UserDocument Komponenten DLL EXE
Diese Seite wurde zuletzt aktualisiert am 03.05.2001

Diese Seite wurde zuletzt aktualisiert am 03.05.2001
Aktuell im ABOUT Visual Basic-MagazinGrundlagenwissen und TechnologienKnow How, Tipps und Tricks rund um Visual BasicActiveX-Komponenten, Controls, Klassen und mehr...AddIns für die Visual Basic-IDE und die VBA-IDEVBA-Programmierung in MS-Office und anderen AnwendungenScripting-Praxis für den Windows Scripting Host und das Scripting-ControlTools, Komponenten und Dienstleistungen des MarktesRessourcen für Programmierer (Bücher, Job-Börse)Dies&Das...

Themen und Stichwörter im ABOUT Visual Basic-Magazin
Code, Beispiele, Komponenten, Tools im Überblick, Shareware, Freeware
Ihre Service-Seite, Termine, Job-Börse
Melden Sie sich an, um in den vollen Genuss des ABOUT Visual Basic-Magazins zu kommen!
Informationen zum ABOUT Visual Basic-Magazin, Kontakt und Impressum

Zurück...

Arbeiter-Klasse

Zurück...


Anzeige

(-hg) mailto:hg_workerclass@aboutvb.de

Eine Klasse, die selbsttätig im Hintergrund arbeitet, ihr nach und nach zugeschobene Aufgaben der Reihe nach abwickelt, und die erledigten Jobs auf Abruf zur Verfügung stellt? Die sich die Aufgaben gegebenenfalls sogar selbst abholt und genau auch selbst wieder abliefern kann? Zugegeben, das mag sich vielleicht etwas abstrakt und auch kompliziert anhören. Aber ich bin sicher, dass Ihnen der eine oder andere Verwendungszweck aufgehen wird, wenn Sie das zu diesem Artikel herunterladbare Beispiel ausprobiert haben. Und wenn Sie gesehen, haben, wie einfach die Handhabung ist...

Das Grundprinzip ist sogar wirklich sehr simpel. Man nehme zwei Collections ("ToDo" und "Ready") und einen Timer. Dazu eine Klasse ("Job"), deren Instanzen mit Daten gefüttert und in der ersteren Collection abgelegt werden. Im Timer-Ereignis wird nun jeweils eine Instanz aus dieser ToDo-Collection geholt und eine bestimmte Methode der Klasse zur Bearbeitung der Daten, mit der die jeweilige Instanz gefüttert worden war, aufgerufen. Dann wird die Instanz in der Ready-Collection abgelegt und steht damit zur Abholung bereit.

Die Job-Klasse (clsJob) könnte beispielsweise so aussehen - ihre Aufgabe wäre hier, einen Datenwert zu verdoppeln:

Private pData As Long
Private pResult As Long

Public Sub SetData(ByVal Data As Long)
  pData = Data
End Sub

Public Sub Execute()
  pResult = pData * 2
End Sub

Public Property Get Result() As Long
  Result = pResult
End Sub

Der Code zur Verwendung in einem Form, auf der besagte Timer platziert ist, könnte nun (ein wenig vereinfacht dargestellt) wie folgt aussehen - die Ausgangsdaten befinden sich hier in einer ListBox, die Resultate sollen in einer anderen ListBox abgelegt werden:

Private mToDo As Collection
Private mReady As Collection

Private Sub Form_Load()
  ' ListBox1 mit 500 Zahlen füllen...
  Set mToDo = New Collection
  Set mReady = New Collection
End Sub

Private Sub cmdAdd_Click()
  Dim i As Integer
  Dim nJob As clsJob

  With ListBox1
    For i = 1 To 25
      If .ListCount Then
        Set nJob = New clsJob
        nJob.SetData .List(i)
        mToDo.Add nJob
      Else
        Exit For
      End If
    Next 'i
  End With
End Sub

Private Sub Timer1_Timer()
  Dim nJob As clsJob

  With mToDo
    If .Count Then
      Set nJob = .Item(1)
      .Remove 1
      nJob.Execute
      mReady.Add nJob
    End
  End With
End Sub

Private Sub cmdResults_Click()
  With mReady
    If .Count Then
      ListBox2.AddItem .Item(1).Result
      .Remove 1
    End If
  End With
End Sub

Über die cmAdd-Schaltfläche fügen Sie neue Jobs dem wartenden Stapel hinzu - entweder gleichen einen ganzen Pulk von neuen Jobs, oder auch einzelne Jobs. Diese Jobs brauchen natürlich nicht so trivial wie in diesem Beispiel zu sein. Es können durchaus auch anspruchsvollere Aufgaben von einer solchen Job-Klasse ausgeführt werden, so etwa auch länger andauernde Bearbeitungen von Dateien und dergleichen. Die Detailstruktur einer Job-Klasse kann individuell angelegt werden, mit beliebig vielen weiteren Eigenschaften und Methoden.

Es braucht noch nicht einmal eine spezielle Job-Klasse zu sein. Wenn Sie wissen, was in Visual Basic mit Schnittstellen und deren Implementierung machbar ist (das Thema ist zu umfangreich, um im Rahmen dieses Artikels darauf einzugehen), werden Sie sich vorstellen können, dass Sie jede beliebige Klasse in diesem Sinne "job-fähig" machen können. Genau so gut können Sie auch Instanzen völlig verschiedener Klassen nacheinander auf den ToDo-Stapel legen, solange diese Klassen die job-fähige Schnittstelle implementieren - etwa verschiedene Dateibearbeiter für verschiedene Dateiformate. Natürlich sollten solche Langzeitbearbeiter mit DoEvents-Aufrufen dafür sorgen, dass sie nicht die ganze Anwendung blockieren, oder diese Bearbeiter sind in einer ActiveX-EXE angelegt und können so in einem eigenen Thread im Hintergrund operieren.

So schlicht der oben stehende Form-Modul-Code auch ist - er ist immer noch etwas zu umständlich. Die ganze Abwicklung können Sie auch in eine eigenständige Klasse packen, gemäß der Überschrift dieses Artikels in eine "Arbeiter-Klasse" (clsWorker):

Private mToDo As Collection
Private mReady As Collection

Private Sub Class_Initialize()
  Set mToDo = New Collection
  Set mReady = New Collection
End Sub

Public Sub Add(Job As clsJob)
  mReady.Add Job
End Sub

Public Sub Trigger()
  Dim nJob As clsJob

  With mToDo
    If .Count Then
      Set nJob = .Item(1)
      .Remove 1
      nJob.Execute
      mReady.Add nJob
    End If
  End With
End Sub

Public Sub Fetch(Job As clsJob)
  With mReady
    If .Count Then
      Set Job = .Item(1)
      .Remove 1
    End If
  End With
End Sub

Die Trigger-Methode wird direkt vom Timer-Ereignis aufgerufen. Und beim Aufruf der Fetch-Methode brauch Sie lediglich noch darauf zu achten, ob tatsächlich eine Job-Instanz geliefert wird, oder Nothing.

Eingangs hatte ich Ihnen allerdings noch etwas mehr Komfort versprochen, etwa die Selbstabholung oder die automatische Ablieferung. Beide werden über weitere Eigenschaften, AutoFeed und AutoDeliver, angelegt. Setzen Sie eine der Eigenschaften auf 0, so ist das jeweilige Feature ausgeschaltet. Setzen Sie einen Wert größer als 0, legen Sie damit fest, wie viele Jobs auf einmal abgeholt bzw. zugestellt werden sollen.

Die Abholungen erfolgen in der Trigger-Methode vor der nächsten anstehenden Bearbeitung, die Zustellungen erfolgen im Anschluss daran. In beiden Fällen werden Ereignisse (Feed und Deliver) der Klasse ausgelöst, entsprechend den jeweils in den beiden zugehörigen Eigenschaften eingestellten Werten. Das Feed-Ereignis erwartet, dass im Parameter Job eine neue Instanz der Job-Klasse zurückgegeben wird. Das Deliver-Ereignis nimmt eine Instanz (sofern vorhanden) vom Ready-Stapel und übergibt sie in Ihrem Job-Parameter nach draußen.

Public Event Deliver(Job As clsJob)
Public Event Feed(Job As clsJob)

Private pAutoDeliver As Long
Private pAutoFeed As Long

Public Property Get AutoDeliver() As Long
  AutoDeliver = pAutoDeliver
End Property

Public Property Let AutoDeliver(New_AutoDeliver As Long)
  pAutoDeliver = Abs(New_AutoDeliver)
End Property

Public Property Get AutoFeed() As Long
  AutoFeed = pAutoFeed
End Property

Public Property Let AutoFeed(New_AutoFeed As Long)
  pAutoFeed = Abs(New_AutoFeed)
End Property

Die Deklaration der Trigger-Methode ist gegenüber der bereits gezeigten Grundform ein wenig verändert. Sie ist jetzt als Funktion ausgelegt, der der Parameter StopTimer übergeben werden. Der Rückgabewert ist so lange True, wie noch zur Bearbeitung anstehende Jobs im ToDo-Stapel oder bereits erledigte, aber noch nicht abgeholte bzw. zugestellte Jobs im Ready-Stapel vorhanden sind. Somit können Sie anhand des Rückgabewerts entscheiden, ob der Timer abgeschaltet werden kann, so lange die Arbeiter-Klasse nichts mehr zu tun hat. Über den Parameter StopTimer können sie mit dem Wert True die Information übermitteln, dass kein weiterer Nachschub mehr zu erwarten ist. Wenn dann noch erledigte Jobs im Ready-Stapel sind, und AutoDeliver gesetzt ist, werden diese ausgeliefert, auch wenn die Anzahl niedriger ist, als der in AutoDeliver eingestellte Schwellwert.

Public Function Trigger(Optional ByVal StopTimer As Boolean) _
 As Boolean

  Dim nJob As clsJob
  Dim l As Long
  
  With mToDo
    If pAutoFeed Then
      If .Count = 0 Then
        For l = 1 To pAutoFeed
          Set nJob = Nothing
          RaiseEvent Feed(nJob)
          If nJob Is Nothing Then
            Exit For
          Else
            .Add nJob
          End If
        Next 'l
      End If
    End If
    If .Count Then
      Set nJob = .Item(1)
      .Remove 1
      nJob.Execute
      mReady.Add nJob
    End If
    If pAutoDeliver Then
      With mReady
        If (.Count >= pAutoDeliver) Or _
         (StopTimer And (mToDo.Count = 0)) Then
          Do While .Count
            RaiseEvent Deliver(.Item(1))
            .Remove 1
          Loop
        End If
      End With
    End If
    Trigger = (.Count Or mReady.Count)
  End With
End Function

Eine kleine Erweiterung stellt noch die Eigenschaft JobsPerTrigger dar. Sie legt fest, wie viele Jobs während eines Trigger-Aufrufs direkt hintereinander weggearbeitet werden sollen, falls etwa die Bearbeitungszeit jedes einzelnen Jobs ansehbar kürzer als die minimale Timer-Auflösung ist.

Public Property Get JobsPerTrigger() As Long
  JobsPerTrigger = pJobsPerTrigger
End Property

Public Property Let JobsPerTrigger(New_JobsPerTrigger As Long)
  Select Case JobsPerTrigger
    Case Is >= 1
      pJobsPerTrigger = New_JobsPerTrigger
    Case Else
      pJobsPerTrigger = 1
  End Select
End Property

In der Trigger-Methode ändert sich dazu im Mittelteil zur Job-Bearbeitung nur wenig:

  Dim nJobsPerTrigger As Long
' ...
    nJobsPerTrigger = pJobsPerTrigger
    Do While .Count
      Set nJob = .Item(1)
      .Remove 1
      nJob.Execute
      mReady.Add nJob
      nJobsPerTrigger = nJobsPerTrigger - 1
      Debug.Print nJobsPerTrigger
      If nJobsPerTrigger = 0 Then
        Exit Do
      End If
    Loop

Die letzte Verbesserung betrifft den "Motor" des Ganzen, den Timer. Die Bearbeitung des Timer-Ereignisses kann nämlich auch komplett in die Arbeiter-Klasse verlegt werden. Dazu wird in einer zusätzlichen Init-Methode der Timer an die Klasse übergeben und einer in der Klasse deklarierten Ereignisempfänger-Variablen ("WithEvents...") zugewiesen (nebenbei kann dieser Init-Methode noch zugleich ein Wert für JobsPerTrigger optional übergeben werden).

Private WithEvents eTimer As Timer

Public Sub Init(Timer As Timer, _
 Optional ByVal JobsPerTrigger As Variant)

  Set eTimer = Timer
  If Not IsMissing(JobsPerTrigger) Then
    pJobsPerTrigger = JobsPerTrigger
  End If
End Sub

Private Sub Class_Terminate()
  If Not (eTimer Is Nothing) Then
    eTimer.Enabled = False
    Set eTimer = Nothing
  End If
End Sub

Für das Timer-Ereignis gibt es nun eine eigene Ereignisprozedur innerhalb der Klasse. Damit außerhalb der Klasse gegebenenfalls eine Zähler- oder Fortschrittsanzeige aktualisiert werden kann, wird hier nach dem internen Aufruf der Trigger-Methode das weitere Klassen-Ereignis AfterTrigger ausgelöst. Der Einfachheit halber werden von ihm die aktuellen "Füllhöhen" der Stapel mToDo und mReady als Parameter nach draußen gereicht. Da hier der Aufruf der Trigger-Methode intern erfolgt, entfällt hier auch die direkte Rückmeldemöglichkeit zur Timer-Abschaltung. Daher verfügt die AfterTrigger-Ereignis über den weiteren Parameter StopTimer. Er erfüllt den gleichen Zweck wie beim externen Aufruf der Trigger-Methode. Da das Ereignis jedoch erst nach dem Trigger-Aufruf ausgelöst wird, kommt der Wert gewissermaßen "zu spät", um noch die ansonsten noch innerhalb der Trigger-Methode mögliche letzte Auslieferungsaktion anzustoßen, wenn in StopTimer True zurückgegeben wurde. Die Auslieferung ist dazu in eine private Prozedur verlagert worden, die nun sowohl aus der Trigger-Methode als auch von hier aus aufgerufen werden kann. Anschließend wird erneut das AfterTrigger-Ereignis ausgelöst - in erster Linie, damit der nun letzte Stand außerhalb der Klasse bekannt wird. Wird nun hier StopTimer mit True zurückgegeben, und sind beide Stapel leer, wird der Timer abgeschaltet. Da die Trigger-Methode weiterhin öffentlich bleibt, können Sie auch grundsätzlich auf einen Timer verzichten und den Aufruf von beliebigen anderen Ereignissen abhängig machen und manuell aus Ihrem Code heraus vornehmen.

Private Sub eTimer_Timer()
  Dim nStopTimer As Boolean
  
  Me.Trigger
  With mToDo
    RaiseEvent AfterTrigger(.Count, mReady.Count, nStopTimer)
    If nStopTimer Then
      zAutoDeliver True
      RaiseEvent AfterTrigger(.Count, mReady.Count, nStopTimer)
    End If
    eTimer.Enabled = (Not nStopTimer Or .Count Or mReady.Count)
  End With
End Sub

Private Sub zAutoDeliver(ByVal StopTimer As Boolean)
  If pAutoDeliver Then
    With mReady
      If (.Count >= pAutoDeliver) Or _
       (StopTimer And (mToDo.Count = 0)) Then
        Do While .Count
          RaiseEvent Deliver(.Item(1))
          .Remove 1
        Loop
      End If
    End With
  End If
End Sub

Als letzte Kleinigkeit bleibt noch die Eigenschaft AutoTrigger anzuführen, über die der interne Timer, sofern zugewiesen, ein- oder ausgeschaltet werden kann.

Public Property Get AutoTrigger() As Boolean
  If Not (eTimer Is Nothing) Then
    AutoTrigger = eTimer.Enabled
  End If
End Property

Public Property Let AutoTrigger(New_AutoTrigger As Boolean)
  If Not (eTimer Is Nothing) Then
    eTimer.Enabled = New_AutoTrigger
  End If
End Property

Beispiel-Projekt WorkerTest (workerclass.zip - ca. 5,5 KB)


Artikel
Zum Download-Bereich dieses Artikel
Mail an den Autor dieses Artikels

KnowHow
Zur KnowHow-Übersicht

KnowHow-Themen
Themen - Allgemeines
Themen - Entwicklungsumgebung (VB-IDE)
Themen - Forms
Themen - Steuerelemente (Controls)
Themen - Grafik
Themen - Dateien
Themen - UserControls
Themen - Einsteiger-Tipps
Themen - Wussten Sie...?

Übersicht nach Titeln in alphabetischer Reihenfolge
Übersicht nach Erscheinungsdatum

Schnellsuche




Zum Seitenanfang

Copyright © 1999 - 2017 Harald M. Genauck, ip-pro gmbh  /  Impressum

Zum Seitenanfang

Zurück...

Zurück...

Download Internet Explorer