2009-10-14 11 views
10

Mam obiekt, który wymaga dużo inicjalizacji (1-2 sekund na silnej maszynie). Jednak po jej zainicjowaniu zajmuje to tylko około 20 milisekund, aby wykonać typową "pracę":Właściwy sposób na nieskończoną wątek pracownika?

Aby zapobiec ponownej inicjalizacji za każdym razem, gdy aplikacja chce z niej korzystać (może to być 50 razy na sekundę lub nie w ogóle przez kilka minut w typowym użytkowaniu) postanowiłem nadać mu kolejkę zadań i uruchomić ją we własnym wątku, sprawdzając, czy nie ma dla niej żadnej pracy w que. Jednak nie jestem całkowicie pewien, jak utworzyć wątek, który działa nieokreślony z lub bez pracy.

Oto co mam do tej pory, każda krytyka jest mile widziana

private void DoWork() 
    { 
     while (true) 
     { 
      if (JobQue.Count > 0) 
      { 
       // do work on JobQue.Dequeue() 
      } 
      else 
      { 
       System.Threading.Thread.Sleep(50); 
      } 
     } 
    } 

Po myśli: Myślałam może muszę zabić ten wątek wdzięcznie INSEAD pozwolić działać wiecznie, więc myślę, że będę dodać Typ zadania, który mówi, że wątek się kończy. Dowiedziałem się też, jak zakończyć wątek.

Odpowiedz

19

Ty potrzeba do lock tak, więc można Wait i Pulse:

while(true) { 
    SomeType item; 
    lock(queue) { 
     while(queue.Count == 0) { 
      Monitor.Wait(queue); // releases lock, waits for a Pulse, 
           // and re-acquires the lock 
     } 
     item = queue.Dequeue(); // we have the lock, and there's data 
    } 
    // process item **outside** of the lock 
} 

z dodawać jak:

lock(queue) { 
    queue.Enqueue(item); 
    // if the queue was empty, the worker may be waiting - wake it up 
    if(queue.Count == 1) { Monitor.PulseAll(queue); } 
} 

Można również zajrzeć do this question, co ogranicza wielkość kolejki (blokowanie, jeśli jest zbyt pełna).

+0

Klasyczna kolejka, blokada i pulsowanie. –

+0

+1 bardzo ładne ... –

+0

Po co pisać coś, co zostało już napisane? Pfx, kochanie. – Will

2

Potrzebujesz prymitywu synchronizacji, np. WaitHandle (spójrz na metody statyczne). W ten sposób możesz "zasygnalizować" wątkowi roboczemu, że jest praca. Sprawdza kolejkę i kontynuuje pracę, dopóki kolejka nie będzie pusta, i w tym czasie oczekuje, aby muteks ponownie ją zasygnalizował.

Dodać jeden z elementów pracy jest komenda rzucić też tak, że można sygnalizować wątku roboczego, gdy nadszedł czas, aby zamknąć wątek

1

W większości przypadków Robiłem to dość podobny do How You” skonfigurowane - ale nie w tym samym języku. Miałem tę zaletę, że pracowałem ze strukturą danych (w Pythonie), która blokuje wątek, dopóki przedmiot nie zostanie umieszczony w kolejce, negując potrzebę wywołania snu.

Jeśli .NET zapewnia taką klasę, zajrzę do jej użycia. Blokowanie wątków jest znacznie lepsze niż wątku obracającego się podczas wywoływania snu.

Zadanie, które można przekazać, może być tak proste, jak "zero"; jeśli kod otrzyma wartość zerową, wie, że nadszedł czas, aby wyrwać się z czasu i wrócić do domu.

1

Uchwyć Parallel Framework. Ma numer BlockingCollection<T>, który można wykorzystać jako kolejkę zadań. Jak chcesz używać to:

  1. Tworzenie BlockingCollection < T>, która będzie trzymać swoje zadania/zadań.
  2. Tworzenie pewne wątki, które mają niekończącą się pętlę (while (true) {// dostać pracę poza kolejce)
  3. ustawić tematów wykraczających
  4. Dodaj prace do kolekcji, gdy pochodzą one dostępne

Wątki będą blokowane do momentu pojawienia się elementu w kolekcji. Każdy, kto to zrobi, to dostanie (zależy od procesora). Używam tego teraz i działa świetnie.

Ma również tę zaletę, że polega na tym, że MS pisze ten wyjątkowo nieprzyjemny fragment kodu, w którym wiele wątków ma dostęp do tego samego zasobu. I kiedykolwiek możesz poprosić kogoś innego o napisanie, że powinieneś iść na to. Zakładając, że mają więcej zasobów technicznych/testowych i więcej doświadczenia niż ty.

+0

Czy masz na myśli CTP oznaczone "To CTP jest tylko do celów testowych."? Nie jestem pewien, czy to dobra rekomendacja ... w wersji 4.0, dobrze - ale to jest wersja beta! –

1

Jeśli naprawdę nie musisz mieć wyjścia wątku (i po prostu chcesz, aby nie utrzymywał on działającej aplikacji), możesz ustawić wartość true dla Thread.IsBackground, która zakończy się po zakończeniu wątków innych niż tła. Will i Marc mają dobre rozwiązania do obsługi kolejki.

1

Zaimplementowałem kolejkę zadań w tle bez użycia jakiejkolwiek pętli while, pulsowania lub czekania, a nawet dotykania obiektów w ogóle. I wydaje się, że działa. (Mam na myśli to, że pracowałem w środowiskach produkcyjnych, wykonując tysiące zadań dziennie przez ostatnie 18 miesięcy bez żadnego nieoczekiwanego zachowania). Jest to klasa o dwóch znaczących właściwościach: Queue<Task> i BackgroundWorker. Istnieją trzy ważne sposoby, skrócone tutaj:

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e) 
{ 
    if (TaskQueue.Count > 0) 
    { 
     TaskQueue[0].Execute(); 
    } 
} 

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
{ 
    Task t = TaskQueue[0]; 

    lock (TaskQueue) 
    { 
     TaskQueue.Remove(t); 
    } 
    if (TaskQueue.Count > 0 && !BackgroundWorker.IsBusy) 
    { 
     BackgroundWorker.RunWorkerAsync(); 
    } 
} 

public void Enqueue(Task t) 
{ 
    lock (TaskQueue) 
    { 
     TaskQueue.Add(t); 
    } 
    if (!BackgroundWorker.IsBusy) 
    { 
     BackgroundWorker.RunWorkerAsync(); 
    } 
} 

To nie tak, że nie ma oczekiwania i pulsowanie. Ale to wszystko dzieje się wewnątrz BackgroundWorker. To po prostu budzi się, gdy zadanie jest zrzucane w kolejce, działa, dopóki kolejka nie jest pusta, a następnie wraca do trybu uśpienia.

Nie jestem ekspertem od gwintowania. Czy jest jakiś powód, aby zadziwić się z System.Threading dla problemu takiego jak ten, jeśli użyjesz BackgroundWorker?

+0

Ogólnie, zgadzam się, że klasa BackgroundWorker jest bardzo często prostszym wyborem do zarządzania zadaniem w tle. W takim przypadku muszę zakwestionować niektóre z twoich roszczeń.Używasz wątków obiektów: słowo kluczowe 'lock' jest syntaktycznym cukrem dla' System.Threading.Monitor.Enter() 'i' System.Threading.Monitor.Exit() '. Zamiast pętli while Twój moduł obsługi zdarzeń RunWorkerCompleted wywołuje 'BackgroundWorker.RunWorkerAsync()'. Myślę, że logika pętli oczekiwania/pulsu podczas pętli może być łatwiejsza do wykonania w tym przypadku. –

+0

Wystarczająco fair. Prawdopodobnie wydaje mi się to prostsze, ponieważ nie mam doświadczenia z oczekiwaniem/pulsem. –

Powiązane problemy