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

 

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

Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

Îåêßíçóå áðü ôï ìÝëïò pontifikas. Τελευταία δημοσίευση από το μέλος Aris στις 09-07-2005, 12:54. Υπάρχουν 5 απαντήσεις.
Ταξινόμηση Δημοσιεύσεων: Προηγούμενο Επόμενο
  •  08-07-2005, 19:08 3396

    Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

    Φτοιάχνω ένα thread και με αυτό σηκώνω μια Form από μια άλλη. Αυτό το κάνω για να μπορώ να εναλλάσσω μεταξύ της φόρμας αυτής και της καλούσας.

    Όταν όμως την σκοτώνω, ενώ πράγματι πεθαίνει, το task εξακολουθεί να παραμένει στο taskbar των Windoze και πρέπει να κάνω κλικ επάνω του για να εξαφανιστεί και αυτό.

    Πώς γίνεται να το εξαφανίζω μόλις σκοτώνω την φόρμα?

    Για να κλείσω το thread χρησιμοποιώ

    thr.Abort();
    thr = null;
  •  08-07-2005, 20:06 3399 σε απάντηση της 3396

    Re: Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

    Τι εννοείς όταν λες να "εναλλάσσεις"; Είναι λίγο επικίνδυνα αυτά τα πράγματα...
    Vir prudens non contra ventum mingit
  •  08-07-2005, 22:14 3400 σε απάντηση της 3399

    Re: Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

    Δες τον παρακάτω κώδικα (με εμβόλιμα σχόλια) που υλοποιεί splash screen σε άλλο thread, και προσάρμοσε αναλόγως. Επίσης, δείχνει πως να "σκοτώσουμε" την εφαρμογή στον constructor της κύριας φόρμας (χωρίς try/catch γύρω από το Application.Run και Application.Exit [δεν έχουμε λόγο να "φάμε" exception, αν μπορούμε να το αποφύγουμε ..Cool]).

    Για να τρέξεις τον κώδικα χρειάζεσαι ένα SQL Server με την Northwind. Η φυσιολογική συμπεριφορά γίνεται με τον server να τρέχει και η "προβληματική" με σταματημένο (να μήν μπορεί να φορτώσει τα δεδομένα).

    Προσάρμοσε αναλόγως Sad


    using System;
    using System.Drawing;
    using System.Collections;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Data;

    namespace Splash
    {
     /// <summary>
     /// Summary description for Form1.
     /// </summary>
     public class Form1 : System.Windows.Forms.Form
     {
      private System.Data.SqlClient.SqlDataAdapter sqlDataAdapter1;
      private System.Windows.Forms.DataGrid dataGrid1;
      private System.Data.DataSet dataSet1;
      private System.Data.SqlClient.SqlCommand sqlSelectCommand1;
      private System.Data.SqlClient.SqlConnection sqlConnection2;

      /// <summary>
      /// Required designer variable.
      /// </summary>
      private System.ComponentModel.Container components = null;


     


    Εδώ, είναι το πρώτο κομμάτι από αυτά που χρειαζόμαστε:
    - μία μεταβλήτή για το instance της splash
    - ένα import για την PostMessage
    - η τιμή (constant) του WM_CLOSE





      // SPECIAL STUFF NEEDED
      private static splash spl = null;
      [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)]
      static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
      private const UInt32 WM_CLOSE = 0x0010;


     

    Εδώ έχουμε τον constructor της κύριας φόρμας.
    Ξεκινάει την splash, και μετά προσπαθεί (try/catch) να φορτώσει και να δείξει τον πίνακα Orders
    από την Northwind. Εάν κάτι πάει στραβά, κρατάει το μήνυμα λάθους.
    Εάν φορτώσει σωστά, κάνει και μία μικρή καθυστέρηση για να φανεί αρκετά η splashSmile.
    Στο finally, κλείνει την Splash (η KillMe είναι μία γραμμή, this.Close(); ορισμένη στην splash).
    Εάν προέκυψε λάθος, το δείχνει και στέλνει στον εαυτό της το WM_CLOSE, ώστε το Message loop
    που ξεκίνησε στην main να τερματίσει.



     public Form1()
      {
       //
       // Required for Windows Form Designer support
       //
       InitializeComponent();

       string err = null;
       this.DoSplash();

       try
       {
        this.sqlDataAdapter1.Fill(this.dataSet1);
        this.dataGrid1.DataMember = "Orders";
        this.dataGrid1.Update();

        // simple delay
        DateTime begin = DateTime.Now;
        DateTime end = begin.AddSeconds(3);
        while  (end >= DateTime.Now);
       }
       catch (System.Exception ex)
       {
        err = ex.Message;
       }
       finally
       {
        spl.Invoke(new MethodInvoker(spl.KillMe));
        spl.Dispose();
        spl = null;
       }
       if (err != null)
       {
        MessageBox.Show(null, err, "Something bad at startup", MessageBoxButtons.OK, MessageBoxIcon.Stop);
        // Post WM_CLOSE to self and let the pump handle it
        PostMessage(this.Handle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
       }
            }

      /// <summary>
      /// Clean up any resources being used.
      /// </summary>
      protected override void Dispose( bool disposing )
      {
       if( disposing )
       {
        if (components != null)
        {
         components.Dispose();
        }
       }
       base.Dispose( disposing );
      }

      #region Windows Form Designer generated code
      /// <summary>
      /// Required method for Designer support - do not modify
      /// the contents of this method with the code editor.
      /// </summary>
      private void InitializeComponent()
      {
       this.sqlDataAdapter1 = new System.Data.SqlClient.SqlDataAdapter();
       this.sqlSelectCommand1 = new System.Data.SqlClient.SqlCommand();
       this.sqlConnection2 = new System.Data.SqlClient.SqlConnection();
       this.dataGrid1 = new System.Windows.Forms.DataGrid();
       this.dataSet1 = new System.Data.DataSet();
       ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).BeginInit();
       ((System.ComponentModel.ISupportInitialize)(this.dataSet1)).BeginInit();
       this.SuspendLayout();
       //
       // sqlDataAdapter1
       //
       this.sqlDataAdapter1.SelectCommand = this.sqlSelectCommand1;
       this.sqlDataAdapter1.TableMappings.AddRange(new System.Data.Common.DataTableMapping[] {
                               new System.Data.Common.DataTableMapping("Table", "Orders", new System.Data.Common.DataColumnMapping[] {
                                                        new System.Data.Common.DataColumnMapping("OrderID", "OrderID"),
                                                        new System.Data.Common.DataColumnMapping("CustomerID", "CustomerID"),
                                                        new System.Data.Common.DataColumnMapping("EmployeeID", "EmployeeID"),
                                                        new System.Data.Common.DataColumnMapping("OrderDate", "OrderDate"),
                                                        new System.Data.Common.DataColumnMapping("RequiredDate", "RequiredDate"),
                                                        new System.Data.Common.DataColumnMapping("ShippedDate", "ShippedDate"),
                                                        new System.Data.Common.DataColumnMapping("ShipVia", "ShipVia"),
                                                        new System.Data.Common.DataColumnMapping("Freight", "Freight"),
                                                        new System.Data.Common.DataColumnMapping("ShipName", "ShipName"),
                                                        new System.Data.Common.DataColumnMapping("ShipAddress", "ShipAddress"),
                                                        new System.Data.Common.DataColumnMapping("ShipCity", "ShipCity"),
                                                        new System.Data.Common.DataColumnMapping("ShipRegion", "ShipRegion"),
                                                        new System.Data.Common.DataColumnMapping("ShipPostalCode", "ShipPostalCode"),
                                                        new System.Data.Common.DataColumnMapping("ShipCountry", "ShipCountry")})});
       //
       // sqlSelectCommand1
       //
       this.sqlSelectCommand1.CommandText = "SELECT OrderID, CustomerID, EmployeeID, OrderDate, RequiredDate, ShippedDate, Shi" +
        "pVia, Freight, ShipName, ShipAddress, ShipCity, ShipRegion, ShipPostalCode, Ship" +
        "Country FROM Orders";
       this.sqlSelectCommand1.Connection = this.sqlConnection2;
       //
       // sqlConnection2
       //
       this.sqlConnection2.ConnectionString = "workstation id=\"HOME-ARIS\";packet size=4096;integrated security=SSPI;data source=" +
        "\".\";persist security info=False;initial catalog=Northwind";
       //
       // dataGrid1
       //
       this.dataGrid1.DataMember = "";
       this.dataGrid1.DataSource = this.dataSet1;
       this.dataGrid1.Dock = System.Windows.Forms.DockStyle.Fill;
       this.dataGrid1.HeaderForeColor = System.Drawing.SystemColors.ControlText;
       this.dataGrid1.Location = new System.Drawing.Point(0, 0);
       this.dataGrid1.Name = "dataGrid1";
       this.dataGrid1.ParentRowsVisible = false;
       this.dataGrid1.ReadOnly = true;
       this.dataGrid1.Size = new System.Drawing.Size(760, 566);
       this.dataGrid1.TabIndex = 0;
       //
       // dataSet1
       //
       this.dataSet1.DataSetName = "NewDataSet";
       this.dataSet1.Locale = new System.Globalization.CultureInfo("el-GR");
       //
       // Form1
       //
       this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
       this.ClientSize = new System.Drawing.Size(760, 566);
       this.Controls.Add(this.dataGrid1);
       this.Name = "Form1";
       this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
       this.Text = "Form1";
       ((System.ComponentModel.ISupportInitialize)(this.dataGrid1)).EndInit();
       ((System.ComponentModel.ISupportInitialize)(this.dataSet1)).EndInit();
       this.ResumeLayout(false);

      }
      #endregion

      /// <summary>
      /// The main entry point for the application.
      /// </summary>
      [STAThread]
      static void Main()
      {
       Application.Run(new Form1());
      }

      private static void StartSplash()
      {
       spl = new splash();
       Application.Run(spl);
      }

      private static System.Threading.Thread splThread = null;
      private void DoSplash()
      {
       this.Show();
       this.Update();

       
       splThread = new System.Threading.Thread(new System.Threading.ThreadStart( Form1.StartSplash));
       splThread.Start();
      }

     }
    }


     

    Η DoSplash δείχνει την κύρια φόρμα και μετά ξεκινάει ένα thread (Σημείωση: η μεταβλητή - splThread - δεν χρειάζεται να είναι private static, θα μπορούσε να είναι και εσωτερική στην DoSplash. Την άφησα έτσι για να δοκιμάσω κάποια πράγματα ..).

    Τέλος, η StartSplash (που χρησιμοποιείται για να ξεκινήσει το άλλο Thread καλεί την Application.Run για την splash. Αυτό γιατί έχουμε δύο GUI Threads, με χωριστά Message loop.

    Άρης


    Aris
  •  09-07-2005, 11:55 3403 σε απάντηση της 3396

    Re: Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

    @Kelman
    Εάν, ενώ τρέχεις μια φόρμα, προσπαθήσεις να σηκώσεις μια άλλη(newForm.show()), τότε η καινούρια παίρνει το control και δεν μπορείς να γυρίσεις στην καλούσα όσο η νέα είναι ανοικτή(ή τουλάχιστο εγώ δεν μπόρεσα να βρώ έναν τρόπο Embarrassed).
    Πρέπει να κλείσεις την νέα φόρμα για να επανέρθει το control στην καλούσα.

    Για να το αποφύγω αυτό, φτοιάχνω ένα thread που καλεί την νέα φόρμα και έτσι μπορώ να την περάσω στο background ή να την προσπελάσω κατά βούληση.
    Είναι επικίνδυνο αυτό?

    @Aris
    Το thread το κλείνει η dispose() υποτίθεται?
  •  09-07-2005, 12:36 3404 σε απάντηση της 3403

    Re: Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

    Οι φόρμες είναι objects που δεν είναι ThreadSafe και αυτό πρακτικά σημαίνει ότι πειράζουμε τα controls της φόρμας αποκλειστικά και μόνο από το thread από το οποίο δημιουργήθηκε η φόρμα. Είναι ο απόλυτος κανόνας που πρέπει να τηρείται με θρησκευτική ευλάβεια. Αν παραβιαστεί αυτός ο κανόνας ότι το πρόβλημα είναι ότι δημιουργούνται καταστάσεις απροσδιοριστίας που σε ότι αφορά το debugging είναι εφιάλτης μιας και δεν μπορεί πάντοτε να αναπαραχθεί το πρόβλημα. Ένα μικρό παράδειγμα:

     

    Form1.Textbox1.Text=”test”

    Messagebox.Show(Form1.Textbox1.Text)

     

    Τι θα βγει στο MessageBox αν παραβιαστεί ο κανόνας? «Test»? Όχι… Άγνωστο! Γιατί από την στιγμή που κάνεις assign στο text property μέχρι την στιγμή που το ξαναδιαβάζεις, μπορεί κάποιος άλλος (από άλλο thread) να έχει προλάβει κα να γράψει τη δική του τιμή. Γι αυτό η χρήση threads θέλει πολύ προσοχή, θέλει καλό διάβασμα της θεωρίας και θέλει διπλούς και τριπλούς ελέγχους για την ορθότητα του κώδικα.

     

    Ως, προς το πρόβλημά σου, γιατί δεν δηλώνεις στην καλούμενη φόρμα ένα property στο οποίο θα περνάς ένα reference της καλούσας φόρμας πριν το show; Με αυτόν τον τρόπο θα μπορείς να «βγεις» από  την καλούμενη φόρμα πίσω στην καλούσα. Δοκίμασε αυτή τη τεχνική πριν φτάσεις στην λύση των threads

     


    Vir prudens non contra ventum mingit
  •  09-07-2005, 12:54 3406 σε απάντηση της 3403

    Re: Φορμα Συνδεδεμένη Με Thread.Γιατί δεν πεθαίνει εντελώς?

     pontifikas wrote:

    @Aris
    Το thread το κλείνει η dispose() υποτίθεται?


    Και τα δύο threads είναι GUI Threads και κλείνουν όταν κλείσει το κύριο παράθυρο που τους ανήκει.
    Οπότε, η δεύτερη φόρμα (η splash στο παράδειγμα) κλείνει και "πεθαίνει εντελώς" (=απελευθέρωση του Win32 Handle) καλώντας τις 
       spl.Invoke(new MethodInvoker(spl.KillMe));
       spl.Dispose();
    Αυτό έχει σαν αποτέλεσμα το GUI thread να πεθάνει. Δεν χρειάζεται τίποτε άλλο. Μπορείς να το δεις εάν στήσεις το παράδειγμα (οδηγίες παρακάτω) και προσθέσεις τις γραμμές
       MessageBox.Show(splThread.ThreadState.ToString());
       
    if
    (splThread.IsAlive) splThread.Abort();
    ΚΑΤΩ από την spl = null;
    Θα διαπιστώσεις ότι το δεύτερο thread είναι Stopped και δεν χρειάζεται να κληθεί η Abort() [το if(splThread.IsAlive) είναι πάντα false].

    Ως προς το εάν μπορείς να έχεις δύο ενεργές φόρμες (αυτό φαίνεται να είναι το πρόβλημα, δηλαδή η δεύτερη είναι modal), μήπως την ανοίγεις με .ShowDialog() ;
    Πάντως, σε δοκιμές που έκανα, παίζει κανονικά. Το λεπτό σημείο είναι το ότι, όταν κλείσει η κύρια φόρμα (που είναι το κύριο παράθυρο του thread), κλείνει και η δεύτερη.

    Μήπως θες να ανοιγοκλείνεις δύο φόρμες και να τερματίζεις την εφαρμογή από οποιαδήποτε από αυτές;

    Άρης

    Στήσιμο του παραδείγματος:
    1. New C# Windows Application
    2. Πλήρης αλλαγή του Form1.cs με τον κώδικα (με κλειστό τον designer, έχεις ανοιχτό μόνο το ραράθυρο του κώδικα)
    3. Προσθήκη φόρμας με όνομα "splash"
    4. Προσθήκη στην splash μιάς 
       public void KillMe()
       {
          this.Close();
       }

    5. Διόρθωση του connection string της sqlConnection2 που έχει η Form1 για να βλέπει τον SQL Server που έχει την Northwind DB



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