2012-10-26 23 views
5

OK, oto sytuacja: Mój główny wątek/UI (nazywam go Wątek1) jest używany do pobierania partii obrazów z phsycial skanera dokumentów. Po pobraniu partii, oddzielny wątek "tła" (nazwij to wątek2) rozpoczyna przetwarzanie i zapisywanie obrazów z tej partii.ustaw priorytet dla Równoległy. Dla pętli

Wątek 2 ("wątek tła") korzysta z pętli Parallel.For, która zmniejsza czas przetwarzania/oszczędzania obrazu o 70% w porównaniu do normalnej pętli For. Wydaje się jednak, że maksymalizuje on wszystkie moje procesory, aby wątek1 nie mógł rozpocząć pobierania kolejnych obrazów, dopóki nie zakończy się pętla Parallel.For.

Czy istnieje sposób na "ograniczenie" pętli Parallel.For, aby nie maksymalizować procesorów? Lub, aby ustawić priorytet przetwarzania? Próbowałem ustawić Thread2.Priority = ThreadPriority.Lowest, ale nie ma to wpływu na pętlę. Czy nie rozumiem, jak działa pętla Parallel.For? Czy w jakiś sposób blokuje Thread1?

Oto jak nazywam wątek2 z metody w wątku1.

public void SaveWithSettings(bool save) // method in Thread1 
{ 
    .... 
    Thread thr = new Thread(ThreadWork); // creating new thread (Thread 2) 
    thr.Priority = ThreadPriority.Lowest; // does nothing? 
    thr.Start(new SaveContainer(sc)); // pass a copy as paramater 

    // misc stuff to make scanning possible again 
    numBgw++; 
    twain.RemoveAllImages(); // clear images 
    imagelist.Clear(); // clear imagelist images 
    .... // etc. this all appears to process fine while Thread2 is processing 
} 

Oto moja ThreadWork metoda:

private void ThreadWork(object data) // executing in Thread2 
{ 
    SaveContainer sc = data as SaveContainer; // holds images 

    bool[] blankIndex = new bool[sc.imagelist.Count]; // to use in Parallel.For loop 
    for (int i = 0; i < sc.imagelist.Count; i++) 
     blankIndex[i] = false; // set default value to false (not blank) 

    Parallel.For(0, sc.imagelist.Count, i => // loop to mark blank images 
    { 
     bool x = false; // local vars make loop more efficient 
     x = sc.IsBlankImage((short)i); // check if image at index i is blank 
     blankIndex[i] = x; // set if image is blank 
    } 
    .... // other image processing steps 
} 
+0

Czy na pewno gwintu 1 nie jest zablokowany czekając na gwincie 2 albo coś? Przydałoby się zobaczyć kod tego, co wątek 1 robi podczas oczekiwania. –

+4

Pokaż nam swój kod. – SLaks

+0

Ustawienie priorytetu wątku2 nie będzie miało znaczenia od czasu równoległego. Działa na wątkach puli wątków. –

Odpowiedz

0

OK, wymyśliłem! Zamieszczam to tylko na wypadek, gdyby ktoś nieumyślnie zdarzył się im ...

Okazuje się, że wątek Parallel.For NIE blokował wątku1 (tak, wszystko w porządku). JEDNAK, obiekt w wątku 1 próbował pobrać nową Thread z ThreadPool, podczas gdy pętla została zgnieciona, a więc pojawiło się "opóźnienie". Używam SDK innej firmy, który pozwala mi na interakcję z interfejsem TWAIN i była opcja ScanInNewThread = true, która próbowała chwycić nowy wątek za każdym razem, gdy użytkownik rozpoczął nowy skan (co miało miejsce podczas przerwania pętli) . Byłem w stanie to zmienić, aby pojedynczy (ale wciąż oddzielny) wątek był używany w całej sesji aplikacji zamiast chwytania nowego wątku dla każdej partii skanowania i BANG, bez bardziej zauważalnego opóźnienia.

SO - Morał z tej historii:

istniejących wątków powinny nadal funkcjonować „normalnie” (z wyjątkiem wątku wywołującego pętlę Parallel.For) dopóki nie próbują złapać więcej wątków z ThreadPool podczas gdy pętla się dzieje.

2

Czy istnieje sposób, aby "granica" pętla Parallel.For tak, że nie robi max mój procesor?

Tak, można dodać opcję z MaxDegreeOfParallelism = N.

Lub ustawić priorytet przetwarzania?

Nie. Jest to wątek ThreadPool (pożyczony). Nie zmieniaj jego właściwości. W rzeczywistości jest to pula wątków puli.

A może nie rozumiem, jak działa pętla Parallel.For? Czy w jakiś sposób blokuje Thread1?

Tak, z zewnątrz Parallel.For(...) jest połączeniem blokującym. Więc uruchom go na osobnym zadaniu lub pracy w tle, a nie na głównym wątku.

+0

Uruchomiłem pętlę Parallel.For w wątku 2, który jest moim wątkiem "tła". Moim zdaniem nie powinno to blokować wątku 1, chyba że jakakolwiek metoda w wątku 1, która wywołuje wątek2, czeka na zakończenie wątku2? – fancypants

2

Prostym sposobem jest flaga MaxDegreeOfParallelism w ParallelOptions.

var Options = new ParallelOptions(); 

// Keep one core/CPU free... 
Options.MaxDegreeOfParallelism = Environment.ProcessorCount - 1; 

Paralle.For(0, sc.imagelist.Count, Options, i => // loop to mark blank images 
{ 
    bool x = false; // local vars make loop more efficient 
    x = sc.IsBlankImage((short)i); // check if image at index i is blank 
    blankIndex[i] = x; // set if image is blank 
} 
0

Bez dostępu do całej aplikacji trudno jest dokładnie wiedzieć, co się tutaj dzieje, ale zacznijmy od uszkodzi Parallel.For:

private void ThreadWork(object data) // executing in Thread2 
{ 
    // Thread2 running here 
    SaveContainer sc = data as SaveContainer; // holds images 

    bool[] blankIndex = new bool[sc.imagelist.Count]; // to use in Parallel.For loop 
    for (int i = 0; i < sc.imagelist.Count; i++) 
     blankIndex[i] = false; // set default value to false (not blank) 

    // Thread2 blocks on this call 
    Paralle.For(0, sc.imagelist.Count, i => // loop to mark blank images 
    { 
     // Thread from the pool is running here (NOT Thread2)!!! 

     bool x = false; // local vars make loop more efficient 
     x = sc.IsBlankImage((short)i); // check if image at index i is blank 
     blankIndex[i] = x; // set if image is blank 
    } 
    // Thread2 resumes running here 

    .... // other image processing steps 
} 

więc zmiana priorytetu thread2 nie będzie różnicy, ponieważ i tak jest zablokowane. Jeśli jednak wątek1 nie jest zablokowany, powinien nadal działać. Wątek 1 może nie działać często, co może być Twoim problemem.

Podejściem naiwnym byłoby zrobienie czegoś takiego jak bałagan z priorytetami wątku, liczenie lub dodanie instrukcji Thread.Yield(). Jednak wątki z puli prawdopodobnie już blokują, ponieważ wykonują operacje wejścia/wyjścia.

Najprawdopodobniej to, co musisz zrobić, to zmienić kod, aby pętla ładująca obraz blokowała obraz głównego wątku, uzyskując coś podobnego do System.Threading.WaitHandle lub przenieś więcej pracy, którą robi główny wątek. ładowanie obrazu. Bez refaktoryzacji doświadczenie mówi, że otrzymasz rozwiązanie dostosowane do konkretnej maszyny, na której testujesz, w określonych warunkach pracy, ale kiedy zmieniają się obciążenia lub zmiany sprzętu, twoje "strojenie" zostanie wyłączone.

Przerób kod, aby wykonać więcej pracy w obrębie równoległości. Dla pracowników i zablokuj wątki w wątku głównym, gdy jest praca dla głównego wątku, a będziesz miał rozwiązanie, z którego jesteś dumny.

4
public static void PriorityParallelForeach<T>(this IEnumerable<T> source, Action<T> action, ThreadPriority threadPriority, int? maxDegreeOfParallelism = null) 
    { 
     if (maxDegreeOfParallelism == null || maxDegreeOfParallelism<1) 
     { 
      maxDegreeOfParallelism = Environment.ProcessorCount; 
     } 

     var blockingQueue = new BlockingCollection<T>(new ConcurrentQueue<T>(source)); 
     blockingQueue.CompleteAdding(); 

     var tasks = new List<Task>() ; 

     for (int i = 0; i < maxDegreeOfParallelism; i++) 
     { 
      tasks.Add(Task.Factory.StartNew(() => 
      { 
       while (!blockingQueue.IsCompleted) 
       { 
        T item; 
        try 
        { 
         item = blockingQueue.Take(); 
        } 
        catch (InvalidOperationException) 
        { 
         // collection was already empty 
         break; 
        } 

        action(item); 
       } 
      }, CancellationToken.None, 
        TaskCreationOptions.None, 
        new PriorityScheduler(threadPriority))); 
     } 

     Task.WaitAll(tasks.ToArray()); 

    } 

Lub po prostu:

Parallel.ForEach(testList, item => 
      { 

       var priviousePrio = Thread.CurrentThread.Priority; 
       // Set your desired priority 
       Thread.CurrentThread.Priority = ThreadPriority.Lowest; 

       TestCalc(item); 

       //Reset priviouse priority of the TPL Thread 
       Thread.CurrentThread.Priority = priviousePrio; 
      });