Καλώς ορίσατε στο dotNETZone.gr - Σύνδεση | Εγγραφή | Βοήθεια
σε

 

Αρχική σελίδα Ιστολόγια Συζητήσεις Εκθέσεις Φωτογραφιών Αρχειοθήκες

Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

Îåêßíçóå áðü ôï ìÝëïò cap. Τελευταία δημοσίευση από το μέλος cap στις 21-03-2005, 14:33. Υπάρχουν 8 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  21-03-2005, 14:33 1429

    Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Απο Winforms γενικά δεν είμαι και πολύ δυνατός. Ηθελα να φτιάξω λοιπον μια ωραία φορμίτσα που θα μου λέει "Please wait" όταν εγώ θα έτρεχα μια χρονοβόρα διαδικασία από πίσω...

    Τελικα κατέληξα στη λύση του να βάλω τη διαδικασια σε ένα thread και να μιλάω μέσω ενός module με τη φόρμα χρησιμοποιώντας events.

    Θα ήθελα να μου πείτε αν αυτή η λύση είναι πολύ "μπακάλικη", είναι ομως η μόνη απλή λύση που βρήκα για να έχω και "Please wait" και φορμα η οποία δεν "κολλάει" (hint, hint, εχει και animated εικονιδιάκι και ήθελα να ΚΑΝΕΙ animate το ατιμο :) )

    Ριξτε μια ματια στον κώδικα, για τα σχόλια χρησιμοποιώ VBXC για να πάνε μετά στο nDoc. Δεν είναι τίποτα σπουδαίο, προχειρογραμμένο είναι, απλά για να μου πειτε αν το concept είναι ορθό. Καθε συμβουλή ευπρόσδεκτη!

     

    '/// <summary>
    '/// Starting point of the application.
    '/// Presents a form to prompt the user for db login information, then starts
    '/// executing a long-running procedure while presenting a "please wait" form.
    '/// Finally, it presents the main application form.
    '/// </summary>
    Module mdlMain
        '/// <summary>
        '/// Holds the "Please wait" form instance
        '/// </summary>
        Private m_objFrmWait As frmPleaseWait
        '/// <summary>
        '/// Holds the connection string retrieved by the "Connect to" form
        '/// </summary>
        Private m_strConn As String
        '/// <summary>
        '/// Module starting point
        '/// </summary>
        Public Sub Main()
            Dim strConn As String
            Dim objFrmLogin As New frmLogin
            'Add handlers for DbObjectManager events
            AddHandler DbObjectManager.SPAnalysisStarted, AddressOf DBObjectManager_SpAnalysisStarted
            AddHandler DbObjectManager.SPAnalyzed, AddressOf DbObjectManager_SpAnalyzed
            AddHandler DbObjectManager.SPAnalysisEnded, AddressOf DBObjectManager_SpAnalysisEnded
            'Show the login form
            objFrmLogin.ShowDialog()
            'Assuming that connection info is ok - the login form takes care of that
            If objFrmLogin.DialogResult = DialogResult.OK Then
                'Assign the connection string here
                m_strConn = objFrmLogin.ConnectionString()
                'Get rid of the stupid thing
                objFrmLogin.Close()
                objFrmLogin.Dispose()
                'Create a thread to execute the long-running process and start it
                Dim thrdGetData As New System.threading.Thread(AddressOf GetSPDefs)
                thrdGetData.Start()
                'Ok, the thread has started. For the sake of clarity, we don't have any
                'code to catch Thread Abort exceptions, although we should.
                'Now present the "please wait" form.
                'As you can see, there's a progress bar updated by events thrown by
                'DbObjectManager plus various other bullshit like an animated picture box etc.
                m_objFrmWait = New frmPleaseWait
                m_objFrmWait.ProgrBar.Value = 0
                m_objFrmWait.ProgrBar.Visible = False
                m_objFrmWait.PictureBox1.Refresh()
                m_objFrmWait.lblSpName.Text = "Retrieving stored procedure definitions. Please wait..."
                m_objFrmWait.lblSpName.Refresh()
                'Show dialog so as not to let the execution halt here.
                m_objFrmWait.ShowDialog()
            End If
        End Sub
    <P class=source>    '/// <summary>
        '/// This is our thread. It executes the boring DB query stuff that takes a long time
        '/// If you are curious to find out what it does, it creates a beautiful xml file
        '/// containing all stored procedure dependencies in a database by analyzing the SP code
        '/// not the sysdepends table. It takes quite a while, analyzing SPs one-by-one.
        '/// </summary>
        Private Sub GetSPDefs()
            'Initialize the stupid thing - it's shared giati etsi mas aresei.
            DbObjectManager.Initialize(m_strConn)
            'Do the dirty work
            DbObjectManager.GetData()
        End Sub
        '/// <summary>
        '/// This is an event that shows that an SP has been analyzed. All it does is update
        '/// the progress bar of the "please wait" form
        '/// </summary>
        '/// <param name="current">The number of the current stored procedure analyzed as compared to the total number of SPs to analyze</param>
        '/// <param name="total">The total number of SPs to analyze</param>
        '/// <param name="spName">The name of the specific SP</param>
        Private Sub DbObjectManager_SpAnalyzed(ByVal current As Int32, ByVal total As Int32, ByVal spName As String)
            m_objFrmWait.ProgrBar.Visible = True
            m_objFrmWait.ProgrBar.Maximum = total + 1
            m_objFrmWait.ProgrBar.Value = current
            m_objFrmWait.lblSpName.Text = spName
            m_objFrmWait.lblSpName.Refresh()
        End Sub
        '/// <summary>
        '/// This used to do something, now it doesn't. Just kept it because I may find a
        '/// use for it later on. Shows that the analysis has been started. What does that mean?
        '/// Ok, a long-running query has been run before the analysis starts to gather all SPs and definitions
        '/// ripping off the comments. This event shows that the query has ended and the analysis has started
        '/// </summary>
        Private Sub DBObjectManager_SpAnalysisStarted()
            'Too lazy for now, maybe I'll work on it later
        End Sub
        '/// <summary>
        '/// This is raised at the end of the long-running procedure.
        '/// </summary>
        Private Sub DBObjectManager_SpAnalysisEnded()
            m_objFrmWait.Close()
            'm_objFrmWait.Dispose() 'Kept making me mad, this one.
            'Now show the main application form.
            Dim objFrmMain As New frmMain(m_strConn)
            objFrmMain.ShowDialog()
        End Sub
    End Module


    Σωτήρης Φιλιππίδης

    DotSee Web Services

    View Sotiris Filippidis's profile on LinkedIn

    DotNetNuke them!
  •  22-03-2005, 10:17 1432 σε απάντηση της 1429

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Ορίστε, να έχεις δουλειά να κάνεις και να πέφτουν τέτοια ενδιαφέροντα posts

     

    Λοιπόν, δεν πρόλαβα να δω καλά τον κώδικά σου, αλλά γενικά υπάρχουν συνήθως δύο πράγματα που μας ενδιαφέρουν όταν παίζουμε με τα threads.

     

    Το πρώτο είναι αν θέλουμε να ενημερώνουμε τις τιμές των controls μιας φόρμας μέσα από ένα άλλο thread. Γενικά, οι φόρμες είναι σχεδιασμένες να δουλεύουν single-threaded όπερ σημαίνει ότι ο χρυσός κανόνας είναι «Πάντοτε πειράζουμε τα controls μόνο μέσα από το thread που ανήκουν (αυτό της φόρμας που τα δημιούργησε)». Αν δεν το κάνουμε, στην περίπτωση που απλά διαβάζουμε τα properties τους δεν δημιουργείται μεγάλο πρόβλημα, ωστόσο, μεγάλα projects γράφουμε, πολλές σκοτούρες έχουμε, είναι θέμα χρόνου πότε θα το ξεχάσουμε/μπερδέψουμε/__________ (συμπλήρωσε το κενό) και θα κάνουμε ανάθεση σε property. Για να αποφύγουμε λοιπόν αυτό το πρόβλημα, που όπως όλοι ξέρουμε είναι ό,τι χειρότερο μπορεί να σου τύχει σε debugging, η λύση είναι μία:

     

    Κάθε control έχει ένα property που λέγεται InvokeRequired (ανήκει στην κλάση Control) το οποίο επιστρέφει True αν το τρέχον thread που το κάνει access δεν είναι το thread που δημιούργησε το control. Οπότε, ρωτάμε, και αν είναι True, τότε καλούμε την Invoke  μέθοδο του control χρησιμοποιώντας ένα delegate και ενδεχομένως ένα parameter array τύπου Object. Το μυστικό είναι ότι το control χρησιμοποιεί το delegate για να καλέσει τη μέθοδο στο context του δικού του thread!

     

    Το δεύτερο πράγμα που μας ενδιαφέρει είναι αν θέλουμε να μας επιστρέφονται τιμές από το thread το οποίο δημιουργήσαμε. Για να γίνει κάτι τέτοιο, χρειάζεται να ξέρουμε πότε τελείωσε το thread οπότε σε αυτή την περίπτωση μπορούμε είτε να δουλέψουμε με Raise Event ή με delegates για να περάσουμε τιμές σε κάποια συγκεκριμένη μέθοδο όταν τελειώσει το thread. Εδώ και πάλι χρειάζεται προσοχή όπως παραπάνω σχετικά με το αν πρέπει να πειράξουμε controls γιατί οι event handlers τρέχουν στο context του thread που έκανε raise το event.


    Vir prudens non contra ventum mingit
  •  22-03-2005, 12:03 1433 σε απάντηση της 1432

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

     KelMan wrote:

    Ορίστε, να έχεις δουλειά να κάνεις και να πέφτουν τέτοια ενδιαφέροντα posts



    In fact, δεν είχα δουλειά να κάνω Smile Χεχε, τελευταία η δουλειά μου συνίσταται σε activity diagrams και mindmaps, οπότε δεν έχω υλικό να σας δώσω για εδώ. Που και που ομως "δραπετεύω" με κατι πιό ενδιαφέρον Smile

    Λες τώρα το εξής: 


     KelMan wrote:
     «Πάντοτε πειράζουμε τα controls μόνο μέσα από το thread που ανήκουν (αυτό της φόρμας που τα δημιούργησε)».


    Κρατάω αυτόν τον κανόνα και τον εξετάζω σε συνδυασμό με το δεύτερο πράγμα που λες:

     KelMan wrote:
     

    Το δεύτερο πράγμα που μας ενδιαφέρει είναι αν θέλουμε να μας επιστρέφονται τιμές από το thread το οποίο δημιουργήσαμε. Για να γίνει κάτι τέτοιο, χρειάζεται να ξέρουμε πότε τελείωσε το thread οπότε σε αυτή την περίπτωση μπορούμε είτε να δουλέψουμε με Raise Event ή με delegates για να περάσουμε τιμές σε κάποια συγκεκριμένη μέθοδο όταν τελειώσει το thread. Εδώ και πάλι χρειάζεται προσοχή όπως παραπάνω σχετικά με το αν πρέπει να πειράξουμε controls γιατί οι event handlers τρέχουν στο context του thread που έκανε raise το event.



    Εδώ λοιπόν έχω την εξής απορία: Ο κώδικας που έκανα post συνίσταται από ένα module το οποίο κάνει δύο δουλειές: Α) Σηκώνει ένα thread που κάνει τη "χοντρή δουλειά" και πετάει events (αρχισα, επεξεργάστηκα κάτι, τελείωσα) και Β) Σηκώνει τη "please wait" φόρμα. Μεσα σε αυτό το module έχω ορίσει event handlers για το (Α), οι οποίοι πειράζουν τα controls του (Β).

    Φαίνεται οτι λειτουργεί κανονικά. Αλλα εφόσον οι event handlers όπως λες παραπάνω τρέχουν στο context του thread που έκανε raise το event, τότε ουσιαστικά πειράζω το (Β) από το (Α). Αυτό δεν θα έπρεπε λογικά να λειτουργεί. Η ερώτησή μου είναι: Γιατί δουλεύει ενώ θεωρητικά δεν θα έπρεπε;

    Οσο για το InvokeRequired/BeginInvoke, πολύ χρήσιμα αυτά που έγραψες και θα τα δοκιμάσω μόλις βρεθεί χρόνος.


    Σωτήρης Φιλιππίδης

    DotSee Web Services

    View Sotiris Filippidis's profile on LinkedIn

    DotNetNuke them!
  •  22-03-2005, 12:28 1434 σε απάντηση της 1433

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Αααα... Το μεγάλο ερώτημα... "Γιατί δουλεύει ενώ θεωρητικά δεν θα έπρεπε"

    Χωρίς (ακόμη) να έχω κοιτάξει καλά τον κώδικά σου έχουμε και λέμε:

    Χμμμ... Κατ' αρχήν, τι πάει να πει "δεν θα έπρεπε"; Συνήθως τα προβλήματα ξεκινούν από την στιγμή που αρχίζεις να γράφεις στα properties των controls από δύο (και πάνω) σημεία (threads). Επίσης, είναι πολύ πιθανό να τρέξεις 10 φορές τον κώδικα και να μην βρεις λάθος και να σου χτυπήσει στην 11η. Και μάλιστα, αυτού του είδους τα λάθη είναι δύσκολο να τα αναπαραγάγεις ώστε να κάνεις το debugging.

    Ενδέχεται πάντως να μην έχεις κανένα πρόβλημα αν κάνεις μόνο read τα properties ή τα αλλάζεις μόνο από ένα thread, αλλά όπως και είπα παραπάνω, θέλει προσοχή μην κάνεις κάτι μελλοντικά που θα αλλάξει κάτι από αυτά τα δύο ή ακόμα χειρότερα μην αναλάβει/συνεχίσει κάποιος άλλος τον κώδικά σου που δεν γνωρίζει τους κανόνες. Θα έλεγα να δοκιμάσεις το InvokeRequired για να είσαι στο safe side...


    Vir prudens non contra ventum mingit
  •  22-03-2005, 12:42 1435 σε απάντηση της 1434

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Μου φαίνεται οτι άγγιξα το twilight zone...Smile

    Παντως έχεις δίκιο, πιό ασφαλές είναι αυτό που προτείνεις. Αν έχεις πάντως χρόνο ρίξε μια ματιά στον κώδικα μπας και τελικά δεν είναι έτσι όπως (νομίζω) οτι τα λέω...


    Σωτήρης Φιλιππίδης

    DotSee Web Services

    View Sotiris Filippidis's profile on LinkedIn

    DotNetNuke them!
  •  23-03-2005, 09:22 1440 σε απάντηση της 1435

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Το είδα και όπως σωστά εντόπισες, επειδή πειράζεις το (Β) από το (Α) χρειάζονται μερικές αλλαγές...

        Private Sub DbObjectManager_SpAnalyzed(ByVal current As Int32, ByVal total As Int32, ByVal spName As String)

            If m_objFrmWait.ProgrBar.InvokeRequired Then

                m_objFrmWait.ProgrBar.Invoke(New UpdateProgBarControlDelegate(AddressOf UpdateProgBarControl), New Object() {m_objFrmWait.ProgrBar, current, total})

            Else

                UpdateProgBarControl(m_objFrmWait.ProgrBar, current, total)

            End If

     

            If m_objFrmWait.lblSpName.InvokeRequired Then

                m_objFrmWait.lblSpName.Invoke(New UpdateLabelControlDelegate(AddressOf UpdateLabelControl), New Object() {m_objFrmWait.lblSpName, spName})

            Else

                UpdateLabelControl(m_objFrmWait.lblSpName, spName)

            End If

        End Sub

     

        Delegate Sub UpdateProgBarControlDelegate(ByVal aProgBar As ProgressBar, ByVal current As Int32, ByVal total As Int32)

        Delegate Sub UpdateLabelControlDelegate(ByVal aLabel As Label, ByVal spName As String)

     

        Sub UpdateProgBarControl(ByVal aProgBar As ProgressBar, ByVal current As Int32, ByVal total As Int32)

            aProgBar.Maximum = total + 1

            aProgBar.Value = current

        End Sub

     

        Sub UpdateLabelControl(ByVal aLabel As Label, ByVal spName As String)

            aLabel.Text = spName

        End Sub

    Νομίζω είσαι ΟΚ τώρα...


    Vir prudens non contra ventum mingit
  •  23-03-2005, 10:06 1442 σε απάντηση της 1440

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Νομίζω ότι η διπλή κλήση του InvokeRequired είναι περριτή. Μπορείς να αντικαταστήσεις τα δύο If με ένα 

      If m_objFrmWait.InvokeRequired Then
        ...
      End If

    Η φόρμα και τα control της εκτελούνται στο ίδιο thread, οπότε αρκεί να ελέγξεις αν η φόρμα χρειάζεται Invoke.

    Υπάρχει ένα πολύ καλό βιβλίο για Windows Forms, το Windows Forms Programming in VB.Net του Crhis Sells που εξηγεί το τί και πως, χωρίς να απλοποιεί τα πράγματα. Έχω το αντίστοιχο για C# και το βρίσκω ανεκτίμητο.


    Παναγιώτης Καναβός, Freelancer
    Twitter: http://www.twitter.com/pkanavos
  •  23-03-2005, 10:12 1443 σε απάντηση της 1442

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Ναι, δίκιο έχεις... Εμένα με προβλημάτισε λίγο το αν θα μπορούσε να γίνει το update των controls μέσα από το ίδιο delegate. Θεωρητικά γίνεται αλλά χμμμ... Ναι, νομίζω δεν θα υπάρχει πρόβλημα...


    Vir prudens non contra ventum mingit
  •  23-03-2005, 11:01 1445 σε απάντηση της 1443

    Re: Φορμα "Please wait" κατά την εκτέλεση απαιτητικής σε χρόνο διαδικασίας

    Λοιποον το δοκίμασα και πραγματικά παίζει μια χαρά. Ειναι σαφώς ορθότερος τρόπος τώρα που κατάλαβα τι παίζεται με τα threads και τους event handlers. Ευχαριστώ για την άριστη βοήθεια!

    Σωτήρης Φιλιππίδης

    DotSee Web Services

    View Sotiris Filippidis's profile on LinkedIn

    DotNetNuke them!
Προβολή Τροφοδοσίας RSS με μορφή XML
Με χρήση του Community Server (Commercial Edition), από την Telligent Systems