C# problem z wątkami

0

Cześć,
mam problem z wątkami w C#.
Zadaniem programu jest zamiana kolorowej mapy bitowej w czarnobiałą. GreyScale1 jest funkcją napisaną w C++ i dołączoną za pomocą dll (działa poprawnie).
Mimo wywoływania metody Join(), program kończy się zanim skończą się wszystkie wątki (na obrazie pozostają nie przetworzone paski).
Problematyczny fragment kodu:

int begin = 0;
int end = 0;
int interval = myBitmap.R_tab.Length / trackBar_NumberOfThreads.Value;
Thread[] threadTable = new Thread[trackBar_NumberOfThreads.Value];
for (int i = 0; i < trackBar_NumberOfThreads.Value; i++)
{
    if (begin + end <= myBitmap.R_tab.Length)
        end = begin + interval;
    else
        end = myBitmap.R_tab.Length;

    threadTable[i] = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, begin, end));
    threadTable[i].Start();

    begin = end;
}

foreach(Thread thread in threadTable)
{
    thread.Join();
}

Objaśnienia:

  • myBitmap - klasa do której wczytuję mapę bitową
  • begin, end - zmiene służące do wyliczenie indeksów tablicy na jakiej pracować ma wątek

Co ciekawe deklarując w programie liczbę wątków w taki sposób, wszystko działa poprawnie

Thread t1 = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, 0, myBitmap.B_tab.Length/4));
t1.Start();
Thread t2 = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, myBitmap.B_tab.Length / 4, myBitmap.B_tab.Length/2));
t2.Start();
Thread t3 = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, myBitmap.B_tab.Length / 2, (myBitmap.B_tab.Length / 4)*3));
t3.Start();
Thread t4 = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, ((myBitmap.B_tab.Length / 4) * 3), myBitmap.B_tab.Length));
t4.Start();
t1.Join();
t2.Join();
t3.Join();
t4.Join();


Już drugi dzień nad tym siedzę i nie potrafię znaleźć błędu

0

Jak tworzysz kilka wątków to możesz skorzystać z dobrodziejstw typu List i z metody ForEach, którą udostępnia, dzięki niej kod jest o wiele bardziej przejrzysty

List<Thread> threads = new List<Thread> { 
new Thread(() => metoda()) , 
new Thread(() => metoda()),
 };

threads.ForEach(t => t.Start());
threads.ForEach(t => t.Join());

Co do kodu to nie przyglądałem mu się za bardzo,bo nie mam teraz czasu, ale w drugiej wersji dwa razy startujesz wątek 3, a 4 wcale. I zapewne błąd jest przy wyliczaniu indeksu, a nie wątkach. Jak chcesz sprawdzić kiedy wątek kończy pracę to możesz na końcu metody wywołać sobie metodę Console.WriteLine(), ale jak wywołujesz Join() to wątek głowny na pewno nie zakończy się wcześniej niż pozostałe wątki

0

2ga wersja działa poprawnie, to była tylko literówka przy kopiowaniu kodu

Indeksy wydają się być poprawne - testując program w następujący sposób, gdzie każdy kolejny wątek czeka na poprzedni wszystko działa dobrze

threadTable[i] = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, begin, end));
threadTable[i].Start();
threadTable[i].Join();

0

Sprawdziłem przy pomocy Console.WriteLine(), wszystkie wątki się kończą, ale niekóre zanim wykonają swoje zadanie.
Indeksy są ok (post wyżej)

//edit chyba znalazłem błąd

0

interval to int, a jego wartość pochodzi z dzielenia liczb całkowitych, czyli jest potencjalnie obarczona pewnym błędem. Załóżmy, że liczba wątków to 4, a wielkość tablic nie jest wielokrotnością 4 i ma wartość np. 15. interval będzie miało wartość 3 ((int)(15 / 4) == 3). W takim przypadku pierwszy wątek obrobi elementy 0-2, drugi 3-5, trzeci 6-8, czwarty 9-11. Elementy 12-14 pozostaną niezmienione i być może są tymi Twomi nieprzetworzonymi paskami. Co prawda Twoje odpowiedzi sugerują, że tak nie jest - być może pracujesz z bitmapą, która jest na tyle duża, że ma wymiar będący wielokrotnością liczby wątków - ale na pewno na ten problem też się nadziejesz.
Proponuję Ci użyć debugera, sam wtedy będziesz znajdować źródła błędów. Co prawda debugowanie aplikacji wielowątkowych nie należy do przyjemności, ale na pewno pozwoli Ci szybciej zorientować się co się dzieje, niż czekać na strzały forumowiczów.

1

Błąd pewnie wynika z tego jak działają wyrażenia lambda. Kiedy tworzysz nowe wyrażenie to tak naprawdę jest tworzony nowy typ i jeżeli korzystasz ze zmiennych, które są definiowane poza tym wyrażeniem w tym przypadku begin i end no to w tym typie są tworzone dla nich pola statyczne (chyba, nie pamiętam tego dobrze, ale tu bardziej chodzi o działanie i tak właśnie one działają). W pętli tworzysz kilka wątkow stąd też utworzone pola begin i end będą tak jakby globalne, każdy wątek będzie operował na tym samym polu. Stad też powinienieś je uczynić lokalnymi dla każdego wyrażenia, operować na ich kopii. Tutaj masz kod jak to wykonać

threadTable[i] = new Thread(() =>
int tmpBegin = begin;
int tmpEnd = end;
 GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, tmpBegin, tmpEnd));

To powinno rozwiązać Twój problem.

0

Problem rozwiązany, dzięki za pomoc. Tak jak napisał Manuel.Artificer problem tkwił w dostępności między wątkami do zmiennych begin i end. Kiedy debugowałem program linijka po linijce wszsystkie indeksy były ok. Jednak puszczając program normalnie już nie (stąd program nie przetwarzał całej tablicy). W osobnej pętli wyliczam indeksy i wstawiam je do tablicy, a kolejnej już tylko wywołuję wątki z gotowymi indeksami i wszystko działa. Jeszcze raz dzięki za pomoc.

0

Można to też pewnie zrobić za pomocą Parallel.For zamiast pisać to samo ręcznie.

0

Kurcze pomyliłem się. @suszek33 te zmienne deklarujesz na zewnątrz wyrażenia a nie w nim, bo tak jak to zrobiłem wcześniej to nic to nie daje. Tu jest poprawny kod

for (int i = 0; i < trackBar_NumberOfThreads.Value; i++)
{
.....
int tmpBegin = begin;
int tmpEnd = end;
threadTable[i] = new Thread(() => GreyScale1(myBitmap.R_tab, myBitmap.G_tab, myBitmap.B_tab, tmpBegin, tmpEnd));
.....
}

Tak to jest jak coś się robi na szybko. Jak tak zrobisz powinno działać i nie musisz kombinować na żadnej tablicy w celu uzyskania kopi tych wartości.

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