Operacje na kolekcji w pętli - spowalnianie przez wyświetlanie postępu.

0

Witam.

Jestem w trakcie pisania "wtyczki" do programu magazynowego na którym pracuje.
Sprawa wygląda tak, że napisaną bibliotekę, podpina się pod program i dzięki temu może ona reagować na określone zdarzenia.
Chciałbym żeby w określonej sytuacji zmieniła ona rabaty na wszystkich pozycjach dokumentu.
Zrobiłem to na zasadzie :

for (int i=0; i<IloscPozycji; i++)
{
	pozycja[i].rabat = WyliczRabat(pozycja[i].Id, OdbiorcaId);
}

To działa poprawnie, jednak użytkownik nie widzi co się aktualnie dzieje, a przy większych dokumentach (tzn. dokumenty po 1000 - 2000 pozycji) trwa to trochę, bo wartość rabatów nie jest podawana na sztywno tylko wyliczana przez procedurę SQL (metoda WyliczRabat wywołuję to procedurę).
Pomyślałem, że wyświetleń proste okienko wyświetlające postęp i przerobiłem pętle na coś takiego :

Form form = new Form();
form.TopMost = true;
form.StartPosition = FormStartPosition.CenterScreen;
form.Size = new Size(300 , 200);

Label label = new Label();
form.Controls.Add(label);
label.Dock = DockStyle.Fill;
label.TextAlign = ContentAlignment.MiddleCenter;

form.Show();

for (int i=0; i<IloscPozycji; i++)
{
	pozycja[i].rabat = WyliczRabat(pozycja[i].Id, OdbiorcaId);
	if (i % 100 == 0)
	{
		label.Text = i.ToString() + " /" + oDok.Pozycje.Liczba.ToString();
		Application.DoEvents();
	}
}

Jednak strasznie spowolniło to wykonywanie kodu (wcześniej jedna pozycja przeliczała się średnio 0,08 sekundy teraz 0,16 sekundy, co przy 1500 pozycji daje sporą różnicę....).

Na początku napisałem bez warunku if (i % 100 == 0) - wtedy była zupełna tragedia dlatego odświeżanie zrobiłem co 100.

Pytanie do Was: co zrobić, żeby ten średni obrotu jednej pętli zmniejszyć (a wyświetlanie postępu zostawić).

0
Application.DoEvents();

Dlaczego nie zrobisz tego asynchronicznie?

0

Bez DoEvents() zawartość labela nie odświeżała się podczas "kręcenia" pętli.

0

Zrób to w osobnym wątku, a zmiany w GUI wykonuj za pomocą metody BeginInvoke. Tych zmian oczywiście staraj się robić jak najmniej.

0

Szczerze mówiąc nie do końca wiem jak to zrobić w innym wątku ponieważ na końcu metody obsługującej dane zdarzenie trzeba zwrócić wynik, jeżeli przeniosę pętle do innego wątku i zostawię coś takiego:

public bool MetodaZdarzenia()
{
	  Thread myThread= new Thread(PrzeliczRabaty);
          myThread.Start();

	return true;
}

To przed skończeniem pętli metoda zwróci do programu wynik (choć tak naprawdę jeszcze nie została wykonana bo wątku będzie lecieć pętla)

0

Miałem na myśli to, aby umieścić tylko ten fragment w nowym wątku:

for (int i=0; i<IloscPozycji; i++)
{
    pozycja[i].rabat = WyliczRabat(pozycja[i].Id, OdbiorcaId);
    if (i % 100 == 0)
    {
...

I tak i tak będziesz musiał poczekać na wykonanie GUI (o ile dobrze rozumiem co chcesz osiągnąć i na czym ten plugin ma polegać). Różnica jest tylko taka, że nie będziesz musiał odświeżać co chwilę okna aplikacji za pomocą metody DoEvents. Zamiast tego użyjesz application.run w głównym wątku.

0

O cos takiego chodzi, tylko na innym przykladzie podane:

        private void button1_Click(object sender, EventArgs e)
        {
            button1.Enabled = false;
            Task.Run(() =>
            {
                for (int i = 0; i <= 100; i++)
                {
                    Task.Delay(50).Wait(); // tutaj jakas operacja
                    // zeby nie zamulac tego watku robisz BeginInvoke
                    progressBar1.BeginInvoke(new Action<int>((progress) =>
                    {
                        progressBar1.Value = progress;
                    }), new object[] { i /* to co masz w parametrze tej akcji, czyli de facto progress */ } );
                }
                // tutaj masz juz wszystko zrobione wiec nie zalezy Ci zeby nie zamulac aktualnego watku wiec robisz Invoke
                button1.Invoke(new Action(() => { button1.Enabled = true; }));
            });
        }
0

Dziękuję Panowie, tylko że jest jeden problem... z tego co pamiętam aplikacja wymaga bibliotek z frameworkiem 3.5.
Biblioteka po skompilowaniu musi być zarejestrowana w systemie przez RegAsm.exe /codebase /tlb.
Na próbę zmieniłem framweroka w projekcie na 4.5 (bo chyba System.Threading.Tasks jest w 4.5?), ale teraz nie mogę zarejestrować tej biblioteki
Jedyne zmiany jakie zrobiłem to zmiana na 4.5 i dodanie do referencji Microsoft.CSharp, ale teraz próba rejestracji kończy się błędem:
RegAsm : error RA0000 : Nie można załadować zestawu 'd:\TEST\TEST.dll', ponieważ nie jest to prawidłowy zestaw platformy .NET.


Wiem czemu wywalało błąd podczas rejestracji w systemie.
Używałem C:\Windows\Microsoft.NET\Framework\v2.0.50727\RegAsm.exe
a trzeba było użyć C:\Windows\Microsoft.NET\Framework\v4.0.30319>RegAsm.exe, jednak aplikacja tak jak kojarzyłem nie obsługuje frameworka 4.5... :/

0

Panowie da się to zrobić wykorzystując wątki (System.Threading) ?

0

Próbowałem zrobić coś takiego :

Thread test = new Thread(ProgressBarProgress);   // ProgressBarProgress() zwiększa value progresbara o 1
 for (int i = 1; i <= 100; i++)
{
 test.Start();
}

Kończy się to błędem:
Wątek jest uruchomiony lub został zakończony; nie można uruchomić go ponownie.

Mam przenieść Thread test = new Thread(ProgressBarProgress); do pętli ?

0

Jeżeli ten wątek robi tyle, że updatuje progress bar to robisz to źle. Proponuję takie rozwiązanie:

async Task WyliczRabatAsync(int pozycjaId, int odbiorcaId)
{
   await Task.Run(() => WyliczRabat(pozycjaId, odbiorcaId));
   UpdateProgressBar();
}

UpdateProgressBar będzie wywołane po zakończeniu wątku, ale nie zablokuje wątku głównego. W tej metodzie oczywiście robisz BeginInvoke zwiększając wartość ProgressBar. WyliczRabatAsync wywołujesz jak każdą inną.
To rozwiązanie jest dobre, o ile nie masz zbyt dużo pozycji, bo wtedy odpali się zbyt dużo wątków na raz.

1 użytkowników online, w tym zalogowanych: 0, gości: 1