2009-08-23 16 views
12

Obecnie mam dużą liczbę obliczeń C# (wywołania metod) rezydujących w kolejce, która będzie uruchamiana sekwencyjnie. Każde obliczenie będzie korzystać z usługi o dużym opóźnieniu (sieć, dysk ...).Wzorzec projektowy Alternatywa dla Coroutines

Zamierzałem użyć podprogramów Mono, aby umożliwić następne obliczenia w kolejce obliczeniowej, podczas gdy poprzednie obliczenia czekają na powrót usługi o dużym opóźnieniu. Jednak wolę nie polegać na Mono Coroutines.

Czy istnieje wzór projektu, który można implementować w czystym C#, który umożliwi mi przetwarzanie dodatkowych obliczeń w oczekiwaniu na powrót usług o dużym opóźnieniu?

Dzięki

Aktualizacja:

muszę wykonać ogromną liczbę (> 10000) zadań, a każde zadanie będzie używał jakiś wysoki opóźnieniach usługi. W systemie Windows nie można utworzyć tak wielu wątków.

Aktualizacja:

Zasadniczo muszę wzór, który emuluje zalety (jak poniżej) z tasklets w Stackless Pythonie (http://www.stackless.com/)

  1. Ogromne # zadań
  2. Jeśli bloki zadań Kolejne zadanie w kolejce wykonuje
  3. Bez zmarnowanego cyklu procesora.
  4. Minimalne przełączanie na wysokości między zadaniami
+0

Czy można tu przedstawić mocniejsze uzasadnienie dla korporacji? Wydaje się, że prosi o (zrównoważone) wątki, jak w odpowiedzi dtb. –

+0

Cóż, potrzebuję wykonać ogromną liczbę (> 10000) zadań, a każde zadanie będzie korzystało z usługi o dużym opóźnieniu. W systemie Windows nie można utworzyć tak wielu wątków. – jameszhao00

+0

Brzmi jak zadanie dla ThreadPool, +1 dla jscharf –

Odpowiedz

9

Można symulować współpracę mikroprocesorów przy użyciu IEnumerable. Niestety nie zadziała to w przypadku blokowania interfejsów API, więc musisz znaleźć interfejsy API, które można sondować, lub które mają wywołania zwrotne, które można wykorzystać do sygnalizacji.

Rozważmy metodę

IEnumerable Thread() 
{ 
    //do some stuff 
    Foo(); 

    //co-operatively yield 
    yield null; 

    //do some more stuff 
    Bar(); 

    //sleep 2 seconds 
    yield new TimeSpan (2000); 
} 

C# kompilator rozpakować to na maszynie państwowej - ale wygląd jest to, że spółdzielnia MicroThread.

Wzór jest dość prosty. Zaimplementowałeś "harmonogram", który przechowuje listę wszystkich aktywnych IEnumeratorów. Gdy przechodzi przez listę, "uruchamia" każdą z nich za pomocą metody MoveNext(). Jeśli wartość parametru MoveNext ma wartość false, wątek zakończył się, a program planujący usunie go z listy. Jeśli jest to prawda, program planujący uzyskuje dostęp do właściwości Bieżący w celu ustalenia bieżącego stanu wątku. Jeśli jest to TimeSpan, wątek chce się przespać, a program planujący przeniósł go do kolejki, którą można ponownie wczytać do głównej listy po upływie czasu oczekiwania na sen.

Można użyć innych obiektów zwracających w celu implementacji innych mechanizmów sygnalizacyjnych. Na przykład zdefiniuj rodzaj WaitHandle. Jeśli wątek przyniesie jeden z tych elementów, można go przenieść do kolejki oczekujących, dopóki nie zostanie zasygnalizowany uchwyt. Lub możesz wesprzeć WaitAll, tworząc szereg uchwytów oczekiwania. Możesz nawet wdrożyć priorytety.

Zrobiłem prostą implementację tego programu planującego w około 150LOC, ale nie miałem jeszcze okazji, aby blogować kod. To było dla naszego opakowania PhyreSharp PhyreEngine (które nie będzie publiczne), gdzie wydaje się działać całkiem dobrze, kontrolując kilkaset postaci w jednym z naszych pokazów.Wypożyczyliśmy tę koncepcję z silnika Unity3D - mają kilka dokumentów online, które wyjaśniają je z punktu widzenia użytkownika.

+0

Interesujące rzeczy. Spoglądałem na to wcześniej, ale nie sądzę, by można było właściwie uzyskać efekt z kodu, który ma więcej niż 1 poziom głębokości. To znaczy. jeśli masz funkcję startową, która wywołuje inną funkcję, która się wyczerpuje, wszystko się psuje. – jameszhao00

+0

W większości przypadków możesz po prostu zaimplementować funkcje, które chcesz nazwać jako liczby i foreach + zyskać na nich, choć oczywiście potrzebujesz trochę pośrednictwa, aby uzyskać wartości zwracane - nie pamiętam, czy działają paramery ref z wydajnością, ale * jeśli * nie, istnieje kilka innych sposobów. Możesz przekazać odwołanie do obiektu z polami i użyć go, aby uzyskać wartości z powrotem z funkcji, lub przekazać w lambdas, które mogą ustawić twoich locals, lub filtrować pewien typ od uzyskanych wartości, etc ... –

+0

I.e. Thing return = null; foreach (var o w funkcji ((x) => zwrócony = x)) zwrot plonu o; –

6
+0

Tak, to nie rozwiązuje problemu kontynuacji następnego obliczenia, gdy operacja jest intensywna z opóźnieniem. Równoległość zadań sprawia, że ​​obliczenia równoległe są łatwiejsze. – jameszhao00

+4

Biblioteka równoległa zadań może używać więcej wątków niż rdzeni, więc jeśli zauważy, że używane wątki nie zużywają zbyt wiele czasu procesora, może zaplanować więcej zadań ... Może to prowadzić do nadmiernego IO, więc wymaga ostrożnego strojenia, mam nadzieję, że biblioteka zrobi to za ciebie, ale testowanie i sprawdzanie zawsze jest dobrym pomysłem ... – ShuggyCoUk

+0

Rzeczywiste wątki są zbyt ciężkie dla tego projektu. Potrzebuję zadań obliczeniowych 20k-80k działających jednocześnie. – jameszhao00

1

Czy nie jest to konwencjonalny wykorzystanie multi-gwintowane przetwarzanie?

Zapraszamy do obejrzenia wzorów, takich jak reaktor here

+0

Przepraszamy. Jestem nieco zdezorientowany, jak można go tutaj wykorzystać. – jameszhao00

1

pisania do korzystania Async IO może być wystarczająca.

Może to prowadzić do nasy, trudne do debugowania kodu bez silnej struktury w projekcie.

+0

Na niższej warstwie, tak, będę używać AsyncIO do wysyłania/odbierania pakietów sieciowych. Jednak na wyższych warstwach będę implementował pewien rodzaj synchronicznego RPC. – jameszhao00

5

Polecam pomocą Thread Pool do wykonywania wielu zadań z kolejki od razu w zarządzaniu szarżach listę aktywnych zadań, które żywi się kolejka zadań.

W tym scenariuszu główny wątek roboczy początkowo pop N zadań z kolejki do listy aktywnych zadań, które mają być wysłane do puli wątków (najprawdopodobniej przy użyciu QueueUserWorkItem), gdzie N oznacza zarządzalną kwotę, która nie będzie przeciążać pule wątków, upuszczanie aplikacji z planowaniem wątków i kosztami synchronizacji lub zasysanie dostępnej pamięci z powodu połączonego obciążenia pamięci we/wy dla każdego zadania.

Za każdym razem, gdy zadanie sygnalizuje zakończenie wątku roboczego, można je usunąć z listy aktywnych zadań i dodać kolejną z kolejki zadań do wykonania.

Pozwoli to na przeniesienie zestawu N zadań z kolejki. Możesz manipulować N, aby wpłynąć na charakterystykę wydajności i znaleźć to, co jest najlepsze w danych okolicznościach.

Ponieważ jesteś ostatecznie wąskie gardło przez operacje sprzętowe (dysku I/O i sieci We/Wy, procesor) wyobrażam sobie, że mniejsze jest lepsze. Dwa wątki zadań puli pracujących na dysku I/O najprawdopodobniej nie będą wykonywane szybciej niż jeden.

Można również zaimplementować elastyczność w rozmiarze i zawartości aktywnej listy zadań, ograniczając ją do określonej liczby określonego typu zadania. Na przykład, jeśli pracujesz na maszynie z 4 rdzeniami, może się okazać, że najbardziej wydajną konfiguracją są cztery zadania związane z procesorem działające równolegle wraz z jednym zadaniem związanym z dyskiem i zadaniem sieci.

Jeśli masz już jedno zadanie sklasyfikowane jako zadanie IO dysku, możesz odczekać, aż zostanie ono zakończone przed dodaniem kolejnego zadania dysku IO, a możesz zdecydować o zaplanowaniu zadania związanego z procesorem lub związanego z siecią. W międzyczasie.

Mam nadzieję, że to ma sens!

PS: Czy masz jakieś zależności od kolejności zadań?

+0

Nie. Nie ma wymogu dotyczącego kolejności wykonania. – jameszhao00

+0

Zobaczę, czy to mam. Dla każdego rdzenia pewna liczba wątków będzie znajdować się w puli wątków. Początkowo zadanie jest przypisane do wątku i jest wykonywane. Za każdym razem, gdy blokuje zadanie (I/O, ...), zadanie powiadamia/budzi wątek kontrolera dla tego rdzenia procesora, a kontroler uruchamia nowy wątek lub budzi poprzednio śpiącego. To trwa, dopóki wszystkie zadania nie zostaną przetworzone. – jameszhao00

+0

Trochę cię nie interesuje, ale myślę, że masz w sobie coś z tego. Powinieneś przeczytać dokumentację ThreadPool (lub Google dla niektórych samouczków za pomocą QueueUserWorkItem). Naprawdę nie ma wątków stworzonych dla każdego rdzenia. Pomyśl o ThreadPool jako abstrakcji niezależnej od rdzeni. Po prostu rzucasz na niego wiele zadań, które musisz zaplanować i wykonać jednocześnie, kiedy tylko jest to możliwe (co zwykle się zdarza). – jscharf

2

Powinieneś zdecydowanie sprawdzić Concurrency and Coordination Runtime. Jedna z ich próbek dokładnie opisuje to, o czym mówisz: wzywasz do usług o długich opóźnieniach, a CCR wydajnie pozwala na uruchamianie innego zadania podczas oczekiwania. Może obsłużyć ogromną liczbę zadań, ponieważ nie musi odradzać wątku dla każdego z nich, chociaż użyje wszystkich twoich rdzeni, jeśli o to poprosisz.

0

W rzeczywistości, jeśli używasz jednego wątku do zadania, stracisz grę. Zastanów się, dlaczego Node.js może obsługiwać ogromną liczbę połączeń. Używanie kilku wątków z asynchronicznym IO !!! Asynchroniczne i oczekujące funkcje mogą w tym pomóc.

foreach (var task in tasks) 
{ 
    await SendAsync(task.value); 
    ReadAsync(); 
} 

SendAsync() i ReadAsync() są sfałszowane funkcje ASYNC połączenia IO.

Task parallelism to również dobry wybór. Ale nie jestem pewien, który z nich jest szybszy. Możesz przetestować oba z nich w twoim przypadku.

0

Tak, oczywiście, że możesz. Trzeba tylko zbudować mechanizm dyspozytorski, który oddzwoni do lambda, który podasz i przejdzie do kolejki. Cały kod, który piszę w jedności używa tego podejścia i nigdy nie używam coroutines. Pakuję metody, które używają coroutines, takich jak WWW, żeby się go pozbyć. W teorii, coroutines mogą być szybsze, ponieważ jest mniej narzutów. Praktycznie wprowadzają nową składnię do języka, aby wykonać dość trywialne zadanie, a ponadto nie można poprawnie śledzić śladu stosu przy błędzie we wspólnym rutynie, ponieważ wszystko, co zobaczysz, to -> Dalej. Będziesz musiał następnie zaimplementować możliwość uruchamiania zadań w kolejce w innym wątku. Jednak w najnowszej .net istnieją funkcje równoległe i zasadniczo tworzysz podobną funkcjonalność. Nie byłoby naprawdę wielu linii kodu.

Jeśli ktoś jest zainteresowany, wyślę kod, nie mam go na sobie.