(C#.NET) - Drobny problem z aktualizacją labeli i progressbar na żywo

Cześć wszystkim!

Wiem, ostatnio wrzucam tutaj sporo wątków, ale mam w sumie jeden ostatni problem. Otóż napisałem skrypcik, którego fragment wrzucam poniżej i problem polega na tym, że skrypt jest wykonywany poprawnie, ale wartości labeli itd. aktualizują się dopiero po skończeniu pętli, a chciałbym aby aktualizowały się w trakcie jej wykonywania. Jak to zrobić?

Pętla, której to dotyczy:

  foreach (var file in listBox3.Items)
                {
                    string obj = Convert.ToString(file);
                    try
                    {
                        if (File.Exists(obj))
                        {
                            label4.Text = obj;
                            try
                            {
                                using (FileStream scan_obj = new FileStream(obj, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                                {
                                    System.Threading.Thread.Sleep(123);
                                    if (listBox2.Items.Contains(CheckObject(obj)))
                                    {
                                        listBox4.Items.Add(obj);
                                        string foundcount = Convert.ToString(listBox4.Items.Count);
                                        textBox3.Text = foundcount;
                                        label3.Text = "Liczba znalezionych: " + foundcount;
                                    }
                                }
                            }
                            catch
                            {

                            }
                        }
                    }
                    catch
                    {

                    }
                    int scnnr = Convert.ToInt32(textBox2.Text);
                    scnnr = scnnr * 1 + 1 * 1;
                    textBox2.Text = Convert.ToString(scnnr);
                    label2.Text = "Przeskanowano: " + scnnr + " z " + filecount;
                    int step = Convert.ToInt32(textBox2.Text);
                    int final = Convert.ToInt32(textBox1.Text);
                    int precent = 0;
                    precent = (step * 100) / (final * 1);
                    textBox4.Text = Convert.ToString(precent);
                    progressBar1.Value = precent;
                    if (CheckShieldLauncher.Properties.Settings.Default.ScanType == "Szukanie standardowe")
                    {
                        label7.Text = ("     Szukanie standardowe w toku... (" + textBox4.Text + "%)");
                    }
                    else
                    {
                        label7.Text = ("     Szukanie niestandardowe w toku... (" + textBox4.Text + "%)");
                    }
                }

Pozdrawiam!

W skrócie - musisz to zrobić wielowątkowo - w innym “thread’zie”. Teraz twój kod działa w tym samym wątku co UI, więc nie ma się ono kiedy odświeżyć.
Jest to większy temat, ale tu jest fajnie wytłumaczony w kontekście Windows Forms - https://www.syncfusion.com/ebooks/asynchronous_programming_succinctly

A tak bardziej klasycznie:

private void OryginalnaMetoda()
{
	saveThread = new Thread(() => KodWInnymWatku());
	saveThread.Start();
}


private void KodWInnymWatku()
{
	/* tu coś liczysz */
	this.Invoke((MethodInvoker)delegate
	{
		/* Tu aktualizujesz wartości kontrolek */
	});
	
	/* tu coś liczysz */
	this.Invoke((MethodInvoker)delegate
	{
		/* Tu aktualizujesz wartości kontrolek */
	});
	
	/* tu coś liczysz */
	this.Invoke((MethodInvoker)delegate
	{
		/* Tu aktualizujesz wartości kontrolek */
	});
	
	// itd.
}

Zastosowałem się do Twojej wskazówki i kod wygląda tak:

 private void Timer1_Tick(object sender, EventArgs e)
    {
        progressBar1.Increment(20);

        if (progressBar1.Value == 100)
        {
            timer1.Stop();
            System.Threading.Thread.Sleep(123);
            foreach (var obj in listBox1.Items)
            {
                string dir = Convert.ToString(obj);
                listBox3.DataSource = GetFileList("*.*", dir).ToArray();
            }
           string filecount = Convert.ToString(listBox3.Items.Count);
           textBox1.Text = filecount;
           label2.Text = "Przeszukano: 0 z " + filecount;
            label5.Visible = false;
            timer4.Start();
            progressBar1.Value = 0;
            foreach (var file in listBox3.Items)
            {
                string obj = Convert.ToString(file);
                try
                {
                    if (File.Exists(obj))
                    {
                        label4.Text = obj;
                        try
                        {
                            using (FileStream scan_obj = new FileStream(obj, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                            {
                                System.Threading.Thread.Sleep(123);
                                if (listBox2.Items.Contains(CheckObject(obj)))
                                {
                                    listBox4.Items.Add(obj);
                                    update = new Thread(() => StatusUpdate());
                                    update.Start();
                                }
                            }
                        }
                        catch
                        {

                        }
                    }
                }
                catch
                {

                }
                updateprogress = new Thread(() => ProgressUpdate());
                updateprogress.Start();             
            }
        }
    }
  public void StatusUpdate()
    {
        string foundcount = Convert.ToString(listBox4.Items.Count);
        textBox3.Text = foundcount;
        label3.Text = "Liczba znalezionych: " + threatcount;
    }
    public void ProgressUpdate()
    {
        string filecount = Convert.ToString(listBox3.Items.Count);
        int scnnr = Convert.ToInt32(textBox2.Text);
        scnnr = scnnr * 1 + 1 * 1;
        textBox2.Text = Convert.ToString(scnnr);
        label2.Text = "Przeszukano: " + scnnr + " z " + filecount; //Tutaj leży błąd
        int step = Convert.ToInt32(textBox2.Text);
        int final = Convert.ToInt32(textBox1.Text);
        int precent = 0;
        precent = (step * 100) / (final * 1);
        textBox4.Text = Convert.ToString(precent);
        progressBar1.Value = precent;
        if (CheckShieldLauncher.Properties.Settings.Default.ScanType == "Szukanie standardowe")
        {
            label7.Text = ("     Szukanie standardowe w toku... (" + textBox4.Text + "%)");
        }
        else
        {
            label7.Text = ("     Szukanie niestandardowe w toku... (" + textBox4.Text + "%)");
        }
    }

Jednak teraz mam problemy z błędem: “Nieprawidłowa operacja między wątkami: do formantu ‘label2’ uzyskiwany jest dostęp z wątku innego niż wątek, w którym został utworzony.”. Zaznaczyłem na której linijce mi go wskazuje. Dodatkowo pojawił się drobny problem, że po zliczeniu obiektów do przeszukania program nie wskazuje liczby obiektów do przeszukania na starcie tylko zostaje z domyślną wartością “label2” a wcześniej to robił. Próbowałem zamieniać te label2 w normalnej klasie w komentarz, ale nie pomaga.

Tak jak pisałem każde odwołanie do kontrolek musisz robić wewnątrz

this.Invoke((MethodInvoker)delegate
{
/* Tu aktualizujesz wartości kontrolek */
});

np.

this.Invoke((MethodInvoker)delegate
{
tbTekst.Text = “aaa”;
});

Błąd mówi jasno w czym jest problem. Powyższy kod powoduje chwilowy “powrót” do wątku na którym działa UI.

Jak już skończysz zabawę z tym programem polecam zapoznać się z dwoma książkami:

  1. https://www.amazon.de/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882/ref=sr_1_1?__mk_pl_PL=ÅMÅŽÕÑ&keywords=clean+code+robert+martin&qid=1555535784&s=gateway&sr=8-1
  2. https://www.amazon.de/Adaptive-Code-patterns-principles-Practices/dp/1509302581/ref=sr_1_1?__mk_pl_PL=ÅMÅŽÕÑ&keywords=adaptive+code&qid=1555535812&s=gateway&sr=8-1

To jak teraz piszesz jest … takie sobie. Wszystko jednak przyjdzie z czasem.

Dobra! Dzięki! To sporo pomogło i chętnie przeczytam te książki. Mam jeszcze pytanie, ponieważ progressBar owszem się aktualizuje z textBoxami w trakcie pracy, ale same labele już nie i dopiero robią to pod koniec. Mimo tego, że są objęte w tej klasie. Jak to zrobić aby aktualizowały się w trakcie?

Task.Factory.StartNew(() =>
{
     for (int i = 0; i < 10; i++)
     {
          // Any GUI control which you want to use within thread,
          // you need Invoke using GUI thread. I have declare a method below for this
          //Now use this method as
          ExecuteSecure(() => label9.Text = "Processing " + i);
          ExecuteSecure(() => progressBar1.Value = i * 10);
          //... other code etc.
          Thread.Sleep(1000);
     }
});


//---
private void ExecuteSecure(Action action)
{
    if (InvokeRequired)
    {
        Invoke(new MethodInvoker(() => action()));
    }
    else
    {
        action();
    }
}

Kod nie jest mój ale wygląda całkiem dobrze.

No niestety nie pomaga ten kodzik.

W sensie nie aktualizuje się czy w ogóle nie działa?

Nie aktualizuje mi cały czas labeli. I tylko labeli to dotyczy.

Mogę zobaczyć jak kod teraz wygląda?

 Task.Factory.StartNew(() =>
                    {
                        string filecount = Convert.ToString(listBox3.Items.Count);
                        int scnnr = Convert.ToInt32(textBox2.Text);
                        scnnr = scnnr * 1 + 1 * 1;
                        textBox2.Text = Convert.ToString(scnnr);
                        ExecuteSecure(() => label2.Text = "Przeszukano: " + scnnr + " z " + filecount);
                        int step = Convert.ToInt32(textBox2.Text);
                        int final = Convert.ToInt32(textBox1.Text);
                        int precent = 0;
                        precent = (step * 100) / (final * 1);
                        textBox4.Text = Convert.ToString(precent);
                        ExecuteSecure(() =>   progressBar1.Value = precent);
                        ExecuteSecure(() => CheckShieldLauncher.Properties.Settings.Default.ScanProgress = Convert.ToString(precent) + "%");
                    });

ProgressBar bez zarzutu, inty też, problem leży z labelami.

Albo oślepłem albo ci pętle ukradli :slight_smile:

Ops, sorki! Moja gafa :smiley:

    private void Timer1_Tick(object sender, EventArgs e)
    {
        progressBar1.Increment(20);

        if (progressBar1.Value == 100)
        {
            timer1.Stop();
            System.Threading.Thread.Sleep(386);
            foreach (var obj in listBox1.Items)
            {
                string dir = Convert.ToString(obj);
                listBox3.DataSource = GetFileList("*.*", dir).ToArray();               
            }
				Task.Factory.StartNew(() =>
                {
                  string filecount = Convert.ToString(listBox3.Items.Count);
              ExecuteSecure(() =>   textBox1.Text = filecount;
                ExecuteSecure(() => label2.Text = "Przeszukano: 0 z " + filecount);
                });		
            label5.Visible = false;
            timer4.Start();
            progressBar1.Value = 0;
            foreach (var file in listBox3.Items)
            {
                string obj = Convert.ToString(file);
                try
                {
                    if (File.Exists(obj))
                    {
                        label4.Text = obj;
                        try
                        {
                            using (FileStream scan_obj = new FileStream(obj, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                            {
                                System.Threading.Thread.Sleep(123);
                                if (listBox2.Items.Contains(CheckObject(obj)))
                                { 
 Task.Factory.StartNew(() =>
                {
                     listBox4.Items.Add(obj);
                                        string foundcount = Convert.ToString(listBox4.Items.Count);
                                       ExecuteSecure(() =>   textBox3.Text = foundcount;
                                        ExecuteSecure(() => label3.Text = "Liczba znalezionych: " + foundcount);
                });										
                                }
                            }
                        }
                        catch
                        {

                        }
                    }
                }
                catch
                {

                }
                Task.Factory.StartNew(() =>
                {
                    string filecount = Convert.ToString(listBox3.Items.Count);
                    int scnnr = Convert.ToInt32(textBox2.Text);
                    scnnr = scnnr * 1 + 1 * 1;
                    ExecuteSecure(() => textBox2.Text = Convert.ToString(scnnr);
                    ExecuteSecure(() => label2.Text = "Przeszukano: " + scnnr + " z " + filecount);
                    int step = Convert.ToInt32(textBox2.Text);
                    int final = Convert.ToInt32(textBox1.Text);
                    int precent = 0;
                    precent = (step * 100) / (final * 1);
                    ExecuteSecure(() =>    textBox4.Text = Convert.ToString(precent);
                    ExecuteSecure(() =>   progressBar1.Value = precent);
                    ExecuteSecure(() => CheckShieldLauncher.Properties.Settings.Default.ScanProgress = Convert.ToString(precent) + "%");
                });
            }
        }
        }
Task.Factory.StartNew(() =>
                {
                     listBox4.Items.Add(obj);
                                        string foundcount = Convert.ToString(listBox4.Items.Count);
                                       ExecuteSecure(() =>   textBox3.Text = foundcount;
                                        ExecuteSecure(() => label3.Text = "Liczba znalezionych: " + foundcount);
                });							
 Task.Factory.StartNew(() =>
                {
                    string filecount = Convert.ToString(listBox3.Items.Count);
                    int scnnr = Convert.ToInt32(textBox2.Text);
                    scnnr = scnnr * 1 + 1 * 1;
                    ExecuteSecure(() => textBox2.Text = Convert.ToString(scnnr);
                    ExecuteSecure(() => label2.Text = "Przeszukano: " + scnnr + " z " + filecount);
                    int step = Convert.ToInt32(textBox2.Text);
                    int final = Convert.ToInt32(textBox1.Text);
                    int precent = 0;
                    precent = (step * 100) / (final * 1);
                    ExecuteSecure(() =>    textBox4.Text = Convert.ToString(precent);
                    ExecuteSecure(() =>   progressBar1.Value = precent);
                    ExecuteSecure(() => CheckShieldLauncher.Properties.Settings.Default.ScanProgress = Convert.ToString(precent) + "%");
                });

Gdzie są te pętle? Po co uruchamiasz coś do zmiany wątku by użyć to tylko raz?

Sorki, to już ze zmęczenia tak. Położenie klas jest okej wg. mnie już. Mimo wszystko nadal labele się nie aktualizują.

 private void Timer1_Tick(object sender, EventArgs e)
        {
            progressBar1.Increment(20);

            if (progressBar1.Value == 100)
            {
                timer1.Stop();
                System.Threading.Thread.Sleep(586);
                foreach (var obj in listBox1.Items)
                {
                    string dir = Convert.ToString(obj);
                    listBox3.DataSource = GetFileList("*.*", dir).ToArray();               
                }
                 Task.Factory.StartNew(() =>
                 {
                     string filecount = Convert.ToString(listBox3.Items.Count);
                     ExecuteSecure(() => textBox1.Text = filecount);
                     ExecuteSecure(() => label2.Text = "Przeszukano: 0 z " + filecount);
                 });
                label5.Visible = false;
                timer4.Start();
                progressBar1.Value = 0;
                foreach (var file in listBox3.Items)
                {
                    string obj = Convert.ToString(file);
                    try
                    {
                        if (File.Exists(obj))
                        {
                            label4.Text = obj;
                            try
                            {
                                using (FileStream scan_obj = new FileStream(obj, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
                                {
                                    System.Threading.Thread.Sleep(123);
                                    if (listBox2.Items.Contains(CheckObject(obj)))
                                    {
                       
                           
                                        Task.Factory.StartNew(() =>
                                        {
                                            ExecuteSecure(() =>  listBox4.Items.Add(obj);
                                            string foundcount = Convert.ToString(listBox4.Items.Count);
                                            ExecuteSecure(() => textBox3.Text = foundcount);
                                            ExecuteSecure(() => label3.Text = "Liczba znalezionych: " + foundcount);
                                        });
                                    }
                                }
                            }
                            catch
                            {

                            }
                        }
                    }
                    catch
                    {

                    }
                 
                    Task.Factory.StartNew(() =>
                    {
                        string filecount = Convert.ToString(listBox3.Items.Count);
                        int scnnr = Convert.ToInt32(textBox2.Text);
                        scnnr = scnnr * 1 + 1 * 1;
                        ExecuteSecure(() =>  textBox2.Text = Convert.ToString(scnnr);
                        ExecuteSecure(() => label2.Text = "Przeszukano: " + scnnr + " z " + filecount);
                        int step = Convert.ToInt32(textBox2.Text);
                        int final = Convert.ToInt32(textBox1.Text);
                        int precent = 0;
                        precent = (step * 100) / (final * 1);
                        textBox4.Text = Convert.ToString(precent);
                        ExecuteSecure(() =>   progressBar1.Value = precent);
                        ExecuteSecure(() => CheckShieldLauncher.Properties.Settings.Default.ScanProgress = Convert.ToString(precent) + "%");
                    });
                }
            }
        }

Mam wrażenie ,że nie do końca rozumiesz o co chodzi

Task.Factory.StartNew(() =>
{
     Uruchamiasz pętle{
to co ma się zmieniać na bieżąco wraz z przebiegiem pętli
}

Witam,

Wy tak na poważnie, zrób to za pomocą docs.microsoft.com/en-us/dotnet/api/system.componentmodel.backgroundworker, praca będzie ładnie wykonana w tle a aktualizacja https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.progressbar będzie bezproblemowo się zmieniała w wątku UI.

Pozdrawiam,

mr-owl

Jeśli można coś zasugerować, to proponuję przejść na async await. Unika się sporo problemów z wątkowym dostępem do kontrolek i działa to zawsze tak samo na pc/xbox.

Dzięki! Poprawiłem to według Twojej wskazówki i wszystko działa świetnie. Przydałaby mi się teraz jedna rzecz jeszcze. Chodzi o to, że jak mam odpaloną tą pętle w tej klasie i próbuję skorzystać z przycisku anulowania i otwierania okna z wynikami to wszystko pięknie się robi tylko za chwilę dostaję komunikat Visual Studio, który wskazuje na to, że okno się zamknęło tylko thread nadal pracuje. Jak zatrzymać ten thread z Task.Factory.StartNew?

Masz przycisk za pomocą którego anulujesz tak?

Spójrz tu https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-cancellation

Ale ogólnie jeśli możesz to pokaż nowy kod może po prostu źle podchodzisz do przerwania operacji?