none
Invoke, Problem mit Thread RRS feed

  • Frage

  • Hallo Forum,
    ich habe ein folgendes Problem.
    Ich kopiere ca. 1,5GB, möchte ein ProgressBalken zeigen und nicht zulassen, dass das Formular "einfriert". Nach mehreren Fragen, Suchen und Finden usw. versuche ich folgende Lösung:

    1. private delegate void KopierenDelegate();
    2. in einem Button "Start" platziere ich u. a. :
    KopierenDelegate kopierenDelegate = new KopierenDelegate(KopierenMitProgressBar);
    kopierenDelegate.Invoke();
    //Eigentliche Routine..
    private void KopierenMitProgressBar()
            {
                //bool result = true;
                try
                {
                    Object[] arr = myAl.ToArray();
                    pBar1.Visible = true;
                    ShowProgress(arr.Length,0);
                    for (int x = 0; x < arr.Length; x++)
                    {
                        string input = arr[x].ToString().Substring(0, arr[x].ToString().IndexOf(","));
                        string target = arr[x].ToString().Substring(arr[x].ToString().IndexOf(",") + 1);
                        File.Copy(input, target, true);
                        ShowProgress(arr.Length, x);

                    }
                    myAl = null;
                    arr= null;
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Fehler beim Kopieren der Installdateien." + ex.Message, "Fehler beim Kopieren.", MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
                    //result = false;
                }
                return; 
            }
    //ShowProgress
    void ShowProgress(int totalLenght, int lenghtSoFar)
            {
                if (pBar1.InvokeRequired == false)
                {
                    pBar1.Maximum = totalLenght;
                    pBar1.Value = lenghtSoFar;
                    pBar1.Step = 1;
                }
            }

    Die Form "friert" trotzdem ein. Ich komme nicht weiter, entweder habe ich von diesem Prinzip nichts verstanden, oder mache alles falsch (oder beides)
    Danke für Eure Hilfe
    Gruß P.


    Donnerstag, 5. Mai 2011 14:52

Antworten

  • Hallo Purclot,

    > mühsam ernährt sich das Eichhörnchen...

    Gut. Dann sieh Dir mal folgenden Code an. Hier werden einige der bisher erwähnten Konzepte implementiert. Im wesentlichen geht es darum zu zeigen, wie man sich den UI-Thread frei halten kann, während die Bearbeitung der Tasks auf einem Hintergrundthread sequentiell erfolgt:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsApplication1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
        }
    
        private void buttonStartInstall_Click(object sender, EventArgs e)
        {
          InstallSettings settings = new InstallSettings { EmailAddress = "user@user.com", ShouldSendEmail = true };
          IConfigurableInstallPackage installPackage = InstallPackageFactory.CreatePackage(settings, this);
          installPackage.ReportPackageProgress += OnReportPackageProgress;
    
          Thread installThread = new Thread(RunInstall);
          installThread.IsBackground = true;
          installThread.Start(installPackage);
        }
    
        void OnReportPackageProgress(object sender, InstallPackageEventArgs e)
        {
          textBox1.Text += String.Format("Current action: {0}, Done: {1}%", e.InstallActionName, e.PercentageDone) + Environment.NewLine;
        }
    
        private void RunInstall(object state)
        {
          IConfigurableInstallPackage installPackage = state as IConfigurableInstallPackage;
          InstallEngine installEngine = new InstallEngine(installPackage);
          installEngine.Start();
        }
      }
    
      class InstallSettings
      {
        public bool ShouldSendEmail { get; set; }
        public string EmailAddress { get; set; }
      }
    
      internal interface IConfigurableInstallPackage
      {
        event EventHandler<InstallPackageEventArgs> ReportPackageProgress;
        Queue<Task> GetInstallationQueue();
      }
    
      class InstallPackageFactory
      { 
        public static IConfigurableInstallPackage CreatePackage(InstallSettings settings, ISynchronizeInvoke synchronizationControl)
        {
          return new ConfigurableSqlServerInstallPackage(settings, synchronizationControl);
        }
      }
    
      class ConfigurableSqlServerInstallPackage : IConfigurableInstallPackage
      {
        private InstallSettings settings;
        private ISynchronizeInvoke synchronizationControl;
    
        public ConfigurableSqlServerInstallPackage(InstallSettings settings, ISynchronizeInvoke synchronizationControl)
        {
          this.settings = settings;
          this.synchronizationControl = synchronizationControl;
        }
    
        public Queue<Task> GetInstallationQueue()
        {
          Queue<Task> installationQueue = new Queue<Task>();
    
          installationQueue.Enqueue(new Task(() => PerformInstall()));
    
          if (this.settings.ShouldSendEmail)
            installationQueue.Enqueue(new Task(() => SendInstallResultsEmail()));
    
          return installationQueue;
        }
    
        public void PerformInstall()
        {
          OnReportPackageProgress("Install SQL-Instance", 0);
          Thread.Sleep(3000); // simulate work
          OnReportPackageProgress("Install SQL-Instance", 100);
        }
    
        public void SendInstallResultsEmail()
        {
          OnReportPackageProgress("Send email", 0);
          Thread.Sleep(1000); // simulate work
          OnReportPackageProgress("Send email", 100);
        }
    
        #region IConfigurableInstallPackage Member
    
        public event EventHandler<InstallPackageEventArgs> ReportPackageProgress;
    
        private void OnReportPackageProgress(string actionName, int percentage)
        {
          EventHandler<InstallPackageEventArgs> handler = ReportPackageProgress;
    
          if (handler != null)
          {
            InstallPackageEventArgs ea = new InstallPackageEventArgs(actionName, percentage);
            synchronizationControl.Invoke(handler, new object[] { null, ea });
          }
        }
    
        #endregion
      }
    
      class Task
      {
        private Action action;
        public bool IsBusy { get; private set; }
    
        public Task(Action action)
        {
          this.action = action;
        }
    
        public void Execute()
        {
          IsBusy = true;
          try { this.action(); }
          finally { IsBusy = false; }
        }
      }
    
      class InstallEngine
      {
        private Queue<Task> taskQueue;
    
        public InstallEngine(IConfigurableInstallPackage installPackage)
        {
          this.taskQueue = installPackage.GetInstallationQueue();
        }
    
        public void Start()
        {
          for (int i = 0; i <= taskQueue.Count; i++)
            taskQueue.Dequeue().Execute();
        }
      }
    
      class InstallPackageEventArgs : EventArgs
      {
        int percentageDone;
        string installActionName;
    
        public InstallPackageEventArgs(string installActionName, int percentageDone)
        {
          this.installActionName = installActionName;
          this.percentageDone = percentageDone;
        }
    
        public int PercentageDone
        {
          get { return this.percentageDone; }
        }
    
        public string InstallActionName
        {
          get { return this.installActionName; }
        }
      }
    }
    

    Natürlich könnte man statt dem new Thread() auch einen BackgroundWorker verwenden (oder ThreadPool.QueueUserWorkItem() oder einen System.Threading.Timer usw). Die Synchronisierung zwischen den Threads erfolgt in diesem Fall über ISynchronizeInvoke, aber auch hier gibt es eine reiche Auswahl an Möglichkeiten.

    Hoffentlich hilft es Dir ein klein bischen weiter.

    Gruß
    Marcel


    • Als Antwort markiert Purclot Mittwoch, 11. Mai 2011 19:29
    Mittwoch, 11. Mai 2011 16:21
    Moderator

Alle Antworten

  • Hallo P.,

    Welchen Sinn hat es denn vom UI-Thread aus Invoke aufzurufen? Das Formular "friert ein", weil der UI-Thread einfach zu beschäftigt ist mit dem Kopieren. Jetzt könntest Du dein Code mit Application.DoEvents() spicken, um wenigstens ab und zu Nachrichten aus der Message Queue zu peeken, aber auch das würde das Formular nur ruckeln und zuckeln lassen. Du mußt Deinen Kopiervorgang auf einem background Thread ausführen, damit die UI reaktionsfähig bleibt. Verwende doch lieber eine BackgroundWorker-Komponente und behandle das Aktualisieren von ProgressBar im ProgressChanged-Ereignishandler [backgroundworker.ReportProgress()].

    (BTW: Der Sinn von ShowProgress() erschließt sich mir nicht ganz. Was würde denn passieren wenn InvokeRequired true zurückgeben würde?)

    Gruß
    Marcel




    Donnerstag, 5. Mai 2011 15:55
    Moderator
  • Hallo Marcel,
    danke für Deine Antwort.
    1. Ich habe das Problem mit dem BackgroundWorker versucht zu lösen. Der BackgroundWorker an sich macht zwar den Job einwandfrei, aber: nach dem Kopieren sollen andere Methoden aufgerufen werden, der Worker "wartet" nicht und es wird sofort die nächste Methode aufgerufen, die eigentlich auf die Resultate des Kopiervorgangs wartet. Das Programm crashed. Du hast bereits von einer Warteschlange gesprochen, die ich aber nicht umsetzen kann. Daher mein Versuch mit dem Invoke. Irgendwie drehe ich mich im Kreis...

    2. mit dem InvokeRequiered==false wollte ich nur festellen, ob ich "auf" dem richtigen Thread bin, wenn nicht, dann eben nichts machen...falsch?

    Gruß P.

    Donnerstag, 5. Mai 2011 16:44
  • Hallo Marcel,
    danke für Deine Antwort.
    1. Ich habe das Problem mit dem BackgroundWorker versucht zu lösen. Der BackgroundWorker an sich macht zwar den Job einwandfrei, aber: nach dem Kopieren sollen andere Methoden aufgerufen werden, der Worker "wartet" nicht und es wird sofort die nächste Methode aufgerufen, die eigentlich auf die Resultate des Kopiervorgangs wartet. Das Programm crashed. Du hast bereits von einer Warteschlange gesprochen, die ich aber nicht umsetzen kann. Daher mein Versuch mit dem Invoke. Irgendwie drehe ich mich im Kreis...

    2. mit dem InvokeRequiered==false wollte ich nur festellen, ob ich "auf" dem richtigen Thread bin, wenn nicht, dann eben nichts machen...falsch?

    Gruß P.

    Hei,

     

    dann warte doch, bis das Kopieren fertig ist. Du kannst Dich dafür einfach benachrichtigen lassen:

    http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.runworkercompleted.aspx


    C.
    Freitag, 6. Mai 2011 06:39
  • Hallo P.,

    > nach dem Kopieren sollen andere Methoden aufgerufen werden, der Worker "wartet" nicht und es wird sofort die nächste Methode aufgerufen, die eigentlich auf die Resultate des Kopiervorgangs wartet.

    1. Egal ob Du über Thread, BackgroundWorker oder ThreadPool.QueueUserWorkItem gehst, eines bleibt immer gleich: Wenn Du den Hautpthread nicht sofort nach dem Abspalten des neuen Threads anhältst [über Thread.Join, AutoResetEven.WaitOne, while(bgw.IsBusy) {Application.DoEvents();} usw.], macht der Hautpthread mit der nächsten Instruktion weiter. Wenn Du den Hautpthread aber blockierst (Thread.Join, WaitHandle.WaitOne) reagiert Deine Benutzeroberfläche nicht mehr, so dass hier eine Warteschleife mit DoEvents() die besseren Ergebnisse bringt. Ganz so in Ordnung ist aber auch das nicht. Deshalb hatte ich Dir suggeriert eine Queue<Task> zu erstellen und dann diese auf Hintergrundthreads sequentiell abzuarbeiten. Wenn Dein Benutzer auf die Schaltfläche "Starten" klickt, erstellst Du die Queue entsprechend der Eingaben und startest einen Threading.Timer. Mehr enthält der OnButtonClick-Handler nicht. Die CallBack-Methode überprüft (auf einem ThreadPoolThread) ob der vorherige Schritt abgeschlossen ist und wenn ja macht mit einem neuen Task aus der Queue weiter . Und die Nachrichten-Pumpe des Hauptthreads bleibt frei und Du kannst aus dem ThreadPoolThread Dein Invoke/BeginInvoke benutzen, um die UI zu aktualisieren.

    2. Du willst ja Dein ProgressBar auch dann aktualisieren, wenn sich die Ausführung auf dem UI-Thread befindet, oder? Invoke ist nur notwendig wenn sich die Ausführung z.Z. auf einem anderen Thread als dem wo das Control erstellt wurde befindet. Normalerweise verwendet man eine Struktur wie:

    if (pBar1.InvokeRequired)
       pBar1.Invoke(ShowProgress);
    else
       ShowProgress();

    Gruß
    Marcel


    Freitag, 6. Mai 2011 07:02
    Moderator
  • hallo,

     

    ich denke Marcel meint mit der Queue einfach das Prinzip des Multithreading.

    Das heißt, du erstellst ein Array von Threads, je nachdem wie viele du brauchst

    und lässt die Methode mit der ProgressBar als Background Thread laufen.

    Die anderen Threads lässte dann starten oder den anderen Thread lässt du

    dann starten, sobald die Der BackgroundThread seine Arbeit getan hat.

    Mit Thread.join sorgst du dafür dass das Programm wartet bis der gestartete

    Thread fertig ist, sonst greifst du evtl. auf Informationen zu, die der Thread

    noch nicht komplett bearbeitet hat. Mit WaitHandle.WaitOne kannst du einen

    Thread auch warten lassen, du musst dir dann nur noch die Methode raussuchen,

    die den Thread wieder "aufweckt". Siehe auch: http://www.codeplanet.eu/tutorials/csharp/64-multithreading-in-csharp.html

    dort steht nochmal gut erklärt, wie Threads funktionieren und wie du diese einsetzen kannst.

     

    Gruß Volker

    Montag, 9. Mai 2011 15:22
  • Hallo Volker,

    Ich denke Marcel meint mit der Queue einfach das Prinzip des Multithreading.

    Nicht ganz, die Queue<T> ist in diesem Fall eine einfache Auflistung von Tasks. 

    Da die einzelnen Aufgaben sequentiell berarbeitet werden sollen, braucht man eigtl. immer nur zwei Threads: Den UI-Thread und einen Hintergrundthread auf dem eine Queue<T> die einzelnen Aktionen speichert.

    Gruß
    Marcel

    Dienstag, 10. Mai 2011 13:07
    Moderator
  • hallo Marcel

     

    da hab ich wohl etwas falsch gelegen. Aber deine Idee hört

    sich sehr gut an. Werd ich bei Gelegenheit mal ausprobieren.

     

    Gruß Volker

    Dienstag, 10. Mai 2011 15:44
  • Hallo,
    mühsam ernährt sich das Eichhörnchen...
    Die Problematik mit der Queue<Thread> habe ich verstanden. Marcel hat vollkommen Recht: eigentlich brauche ich jeweils nur 2 Threads: den Haupt und den jeweiligen "Do_Worker". Ich müßte wahrscheinlich den jeweiligen Thread immer wieder mit dem Hauptthread "joinen" (damit der Hauptthread wartet), dann müßte ich noch eine Liste haben, die die nächsten Schritte beeinhaltet, dann den aktuellen Thread aus der Queue entfernen, den nächsten aus der Liste laden usw. Keine Chance, ich schaffe es nicht, soweit bin ich noch nicht...

    Ich werde es nochmals mit dem BackgroundWorker versuchen...
    Den BackgroundWorker fange ich ab:
    while(!_workFertig)
    {
        Thread.Sleep(50);
    }

    Ich würde gerne alle BackgroundWorker-Methoden in eine Klasse packen, um die Länge der Methoden im Hauptformular auf "nur" 4 km. zu verkürzen, aber da melden sich meine nicht ausreichenden Klassen-Design-Kenntnisse zurück.
    z. B. ich kann doch nicht in eine externe Klasse die Aktualisierung des ProgressBars auf dem Hauptformular auslagern, weil die Worker-Klasse von der Hauptformular-Klasse nichts weiss, es sei denn, ich übergebe der neuen Instanz der Workerklasse die Hauptformularklasse als Parameter..?
    Hmm.


    Dienstag, 10. Mai 2011 20:33
  • Hallo Purclot,

    > mühsam ernährt sich das Eichhörnchen...

    Gut. Dann sieh Dir mal folgenden Code an. Hier werden einige der bisher erwähnten Konzepte implementiert. Im wesentlichen geht es darum zu zeigen, wie man sich den UI-Thread frei halten kann, während die Bearbeitung der Tasks auf einem Hintergrundthread sequentiell erfolgt:

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Windows.Forms;
    using System.Threading;
    
    namespace WindowsFormsApplication1
    {
      public partial class Form1 : Form
      {
        public Form1()
        {
          InitializeComponent();
        }
    
        private void buttonStartInstall_Click(object sender, EventArgs e)
        {
          InstallSettings settings = new InstallSettings { EmailAddress = "user@user.com", ShouldSendEmail = true };
          IConfigurableInstallPackage installPackage = InstallPackageFactory.CreatePackage(settings, this);
          installPackage.ReportPackageProgress += OnReportPackageProgress;
    
          Thread installThread = new Thread(RunInstall);
          installThread.IsBackground = true;
          installThread.Start(installPackage);
        }
    
        void OnReportPackageProgress(object sender, InstallPackageEventArgs e)
        {
          textBox1.Text += String.Format("Current action: {0}, Done: {1}%", e.InstallActionName, e.PercentageDone) + Environment.NewLine;
        }
    
        private void RunInstall(object state)
        {
          IConfigurableInstallPackage installPackage = state as IConfigurableInstallPackage;
          InstallEngine installEngine = new InstallEngine(installPackage);
          installEngine.Start();
        }
      }
    
      class InstallSettings
      {
        public bool ShouldSendEmail { get; set; }
        public string EmailAddress { get; set; }
      }
    
      internal interface IConfigurableInstallPackage
      {
        event EventHandler<InstallPackageEventArgs> ReportPackageProgress;
        Queue<Task> GetInstallationQueue();
      }
    
      class InstallPackageFactory
      { 
        public static IConfigurableInstallPackage CreatePackage(InstallSettings settings, ISynchronizeInvoke synchronizationControl)
        {
          return new ConfigurableSqlServerInstallPackage(settings, synchronizationControl);
        }
      }
    
      class ConfigurableSqlServerInstallPackage : IConfigurableInstallPackage
      {
        private InstallSettings settings;
        private ISynchronizeInvoke synchronizationControl;
    
        public ConfigurableSqlServerInstallPackage(InstallSettings settings, ISynchronizeInvoke synchronizationControl)
        {
          this.settings = settings;
          this.synchronizationControl = synchronizationControl;
        }
    
        public Queue<Task> GetInstallationQueue()
        {
          Queue<Task> installationQueue = new Queue<Task>();
    
          installationQueue.Enqueue(new Task(() => PerformInstall()));
    
          if (this.settings.ShouldSendEmail)
            installationQueue.Enqueue(new Task(() => SendInstallResultsEmail()));
    
          return installationQueue;
        }
    
        public void PerformInstall()
        {
          OnReportPackageProgress("Install SQL-Instance", 0);
          Thread.Sleep(3000); // simulate work
          OnReportPackageProgress("Install SQL-Instance", 100);
        }
    
        public void SendInstallResultsEmail()
        {
          OnReportPackageProgress("Send email", 0);
          Thread.Sleep(1000); // simulate work
          OnReportPackageProgress("Send email", 100);
        }
    
        #region IConfigurableInstallPackage Member
    
        public event EventHandler<InstallPackageEventArgs> ReportPackageProgress;
    
        private void OnReportPackageProgress(string actionName, int percentage)
        {
          EventHandler<InstallPackageEventArgs> handler = ReportPackageProgress;
    
          if (handler != null)
          {
            InstallPackageEventArgs ea = new InstallPackageEventArgs(actionName, percentage);
            synchronizationControl.Invoke(handler, new object[] { null, ea });
          }
        }
    
        #endregion
      }
    
      class Task
      {
        private Action action;
        public bool IsBusy { get; private set; }
    
        public Task(Action action)
        {
          this.action = action;
        }
    
        public void Execute()
        {
          IsBusy = true;
          try { this.action(); }
          finally { IsBusy = false; }
        }
      }
    
      class InstallEngine
      {
        private Queue<Task> taskQueue;
    
        public InstallEngine(IConfigurableInstallPackage installPackage)
        {
          this.taskQueue = installPackage.GetInstallationQueue();
        }
    
        public void Start()
        {
          for (int i = 0; i <= taskQueue.Count; i++)
            taskQueue.Dequeue().Execute();
        }
      }
    
      class InstallPackageEventArgs : EventArgs
      {
        int percentageDone;
        string installActionName;
    
        public InstallPackageEventArgs(string installActionName, int percentageDone)
        {
          this.installActionName = installActionName;
          this.percentageDone = percentageDone;
        }
    
        public int PercentageDone
        {
          get { return this.percentageDone; }
        }
    
        public string InstallActionName
        {
          get { return this.installActionName; }
        }
      }
    }
    

    Natürlich könnte man statt dem new Thread() auch einen BackgroundWorker verwenden (oder ThreadPool.QueueUserWorkItem() oder einen System.Threading.Timer usw). Die Synchronisierung zwischen den Threads erfolgt in diesem Fall über ISynchronizeInvoke, aber auch hier gibt es eine reiche Auswahl an Möglichkeiten.

    Hoffentlich hilft es Dir ein klein bischen weiter.

    Gruß
    Marcel


    • Als Antwort markiert Purclot Mittwoch, 11. Mai 2011 19:29
    Mittwoch, 11. Mai 2011 16:21
    Moderator
  • Hallo Marcel,
    vielen Dank für Deine Antwort und investierte Arbeit. Jetzt werde ich es schaffen. Das Bild mit dem Threading ist jetzt um vieles klarer für mich geworden.

    Gruß P.

    Mittwoch, 11. Mai 2011 19:29