Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

dużo asynchronicznych wywołań - czy to "ładne"

3 views
Skip to first unread message

Paweł Kierski

unread,
Nov 12, 2009, 11:03:09 AM11/12/09
to
Mam taki problem: chcę wywołać kilkadziesiąt razy metodę
synchronicznego API. Na zakończenie wywołań czekam nie więcej niż
ustalony czas - jak któreś z wywołań nie zdąży, to nie ma sprawy.
Interesują mnie tylko wyniki wywołań zakończonych w zadanym czasie, ew.
reszta może zapisać się "na później".

Skonstruowałem taką klasę (przepraszam, że nie daję tu kodu
źródłowego, ale akurat nie mam pod ręką):
- Directory mapujące parametry wywołania na wynik
- listę z wywołaniami w trakcie (lista parametrów)
- zdarzenie ManualResetEvent do czekania
Wywołania są inicjowane przez wywołanie via BeginInvoke metody, która
oprócz wywołania właściwego API robi co trzeba z listą wywołań
w trakcie. Callback aktualizuje listę wywołań w trakcie i słownik
z wynikami. Gdy liczba wywołań w trakcie spada do zera ustawiam event,
na który można czekać z zewnątrz z odpowiednim timeoutem.

Pytanie bardziej szczegółowe - czy to w ogóle ma sens, czy na podobne
przypadki ktoś już wymyślił coś ładniejszego?

Pytanie bardziej ogólne - czy dobrym pomysłem jest w ogóle generowanie
dużych ilości wywołań asynchronicznych? Jakie zasoby to obciąża? Gdzie
jest granica skalowania, np. gdybym chciał zrobić tysiące takich
wywołań?

--
Paweł Kierski
ne...@pkierski.net

Tomasz Muszyński

unread,
Nov 12, 2009, 4:14:00 PM11/12/09
to
W dniu 09-11-12 17:03, Paweł Kierski pisze:

> Mam taki problem: chcę wywołać kilkadziesiąt razy metodę
> synchronicznego API. Na zakończenie wywołań czekam nie więcej niż
> ustalony czas - jak któreś z wywołań nie zdąży, to nie ma sprawy.
> Interesują mnie tylko wyniki wywołań zakończonych w zadanym czasie, ew.
> reszta może zapisać się "na później".

Albo chcesz znać wynik, albo nie. Jeśli jakieś operacje nie są Ci
potrzebne, to powinieneś je przerwać.

> Skonstruowałem taką klasę (przepraszam, że nie daję tu kodu
> źródłowego, ale akurat nie mam pod ręką):
> - Directory mapujące parametry wywołania na wynik
> - listę z wywołaniami w trakcie (lista parametrów)
> - zdarzenie ManualResetEvent do czekania

Z tego nie wiele wynika :) Nie chcesz podać kodu, to napisz chociaż
jakiś symboliczny schemat działania, bo na teraz to podałeś tylko hasła.

> Wywołania są inicjowane przez wywołanie via BeginInvoke metody, która
> oprócz wywołania właściwego API robi co trzeba z listą wywołań
> w trakcie. Callback aktualizuje listę wywołań w trakcie i słownik
> z wynikami. Gdy liczba wywołań w trakcie spada do zera ustawiam event,
> na który można czekać z zewnątrz z odpowiednim timeoutem.

Dalej nie wiele z tego wynika.

> Pytanie bardziej szczegółowe - czy to w ogóle ma sens, czy na podobne
> przypadki ktoś już wymyślił coś ładniejszego?

Może lepiej napisz co chcesz osiągnąć? Na wywołania asynchroniczne są
dziesiątki sposobów.

> Pytanie bardziej ogólne - czy dobrym pomysłem jest w ogóle generowanie
> dużych ilości wywołań asynchronicznych? Jakie zasoby to obciąża? Gdzie
> jest granica skalowania, np. gdybym chciał zrobić tysiące takich
> wywołań?

A my tutaj to wróżymy z fusów? Napisz więc lepiej, co to API robi, gdzie
robi, co Ty robisz i co chcesz osiągnąć.

tm

Paweł Kierski

unread,
Nov 13, 2009, 4:12:26 AM11/13/09
to
Tomasz Muszyński wrote:
> W dniu 09-11-12 17:03, Paweł Kierski pisze:
>> Mam taki problem: chcę wywołać kilkadziesiąt razy metodę
>> synchronicznego API. Na zakończenie wywołań czekam nie więcej niż
>> ustalony czas - jak któreś z wywołań nie zdąży, to nie ma sprawy.
>> Interesują mnie tylko wyniki wywołań zakończonych w zadanym czasie, ew.
>> reszta może zapisać się "na później".
>
> Albo chcesz znać wynik, albo nie. Jeśli jakieś operacje nie są Ci
> potrzebne, to powinieneś je przerwać.

To nie do końca tak - chcę zaprezentować użytkownikowi na jego żądanie
to, co dam radę osiągnąć w określonym czasie. Ale żądanie użytkownika
(sprawdzenia danych) może wyzwolić operacje, których wynik będzie
prezentowany następnym razem (przy następnym żądaniu tego lub innego
użytkownika).

[...]


> Z tego nie wiele wynika :) Nie chcesz podać kodu, to napisz chociaż
> jakiś symboliczny schemat działania, bo na teraz to podałeś tylko hasła.

OK - poniżej. (Nie to, że nie chciałem, po prostu nie miałem pod ręką
8-) )

class ExternalAPI
{
public static string Get(int id)
{
// operacja pobierająca wiadomość o identyfikatorze id
// może trwać dość długo
}
}

class AsyncCache
{
public void StartGetUpdate(int id)
{
lock(messages)
{
if(messages.ContainsKey(id))
return;

lock(requestsInProgress)
{
if(requestsInProgress.Contains(id))
return;

if(requestsInProgress.Count == 0)
requestsFinished.Reset();

requestsInProgress.Add(id);
new GetUpdateHandler(GetUpdateImpl)
.BeginInvoke(id, finishCallback, id);
}
}
}

public string GetUpdate(int id)
{
lock(messages)
{
return messages.ContainsKey(id) ? messages[id] : null;
}
}

private delegate void GetUpdateHandler(int id);

private void finishCallback(IAsyncResult iar)
{
AsyncResult ar = (AsyncResult)iar;
((GetUpdateHandler)ar.AsyncDelegate).EndInvoke(iar);
}

private void GetUpdateImpl(int id)
{
string message = null;
try
{
message = ExternalAPI.Get(id);
}
catch(System.Exception e)
{
}

lock(messages)
{
if(message != null)
messages.Add(id, message);

lock(requestsInProgress)
{
requestsInProgress.Remove(id);

if(requestsInProgress.Count == 0)
requestsFinished.Set();
}
}
}

public void WaitForFinish(int timeout)
{
requestsFinished.WaitOne(timeout);
}

private Dictionary<int, string> messages =
new Dictionary<int, string>();

private List<int> requestsInProgress = new List<int>();

private System.Threading.ManualResetEvent requestsFinished =
new System.Threading.ManualResetEvent(true);
}

użycie:

AsyncCache cache = new AsyncCache();
cache.StartGetUpdate(1);
cache.StartGetUpdate(2);
cache.StartGetUpdate(3);
cache.WaitForFinish(2000);

string result1 = cache.GetUpdate(1);
string result2 = cache.GetUpdate(2);
string result3 = cache.GetUpdate(3);

[...]


> A my tutaj to wróżymy z fusów? Napisz więc lepiej, co to API robi, gdzie
> robi, co Ty robisz i co chcesz osiągnąć.

Ha! Dostałem po uszach 8-) Posypuję głowę popiołem: kod powyżej, cel,
który chcę osiągnąć jeszcze wyżej. Aha - na ExternalAPI nie mam wpływu:
jest jakie jest.

--
Paweł Kierski
ne...@pkierski.net

Tomasz Muszyński

unread,
Nov 13, 2009, 2:50:17 PM11/13/09
to
W dniu 09-11-13 10:12, Paweł Kierski pisze:

> Tomasz Muszyński wrote:
>> W dniu 09-11-12 17:03, Paweł Kierski pisze:
>>> Mam taki problem: chcę wywołać kilkadziesiąt razy metodę
>>> synchronicznego API. Na zakończenie wywołań czekam nie więcej niż
>>> ustalony czas - jak któreś z wywołań nie zdąży, to nie ma sprawy.
>>> Interesują mnie tylko wyniki wywołań zakończonych w zadanym czasie, ew.
>>> reszta może zapisać się "na później".
>>
>> Albo chcesz znać wynik, albo nie. Jeśli jakieś operacje nie są Ci
>> potrzebne, to powinieneś je przerwać.
>
> To nie do końca tak - chcę zaprezentować użytkownikowi na jego żądanie
> to, co dam radę osiągnąć w określonym czasie. Ale żądanie użytkownika
> (sprawdzenia danych) może wyzwolić operacje, których wynik będzie
> prezentowany następnym razem (przy następnym żądaniu tego lub innego
> użytkownika).
>
> [...]
>> Z tego nie wiele wynika :) Nie chcesz podać kodu, to napisz chociaż
>> jakiś symboliczny schemat działania, bo na teraz to podałeś tylko hasła.
>
> OK - poniżej. (Nie to, że nie chciałem, po prostu nie miałem pod ręką
> 8-) )

Na oko to jest ok. Windows sobie spokojnie poradzi z odpalonym 1000
wątków, które zasadniczo nic nie robią tylko czekają na odbiór danych.
Powinieneś jednak założyć, że zdalny system (ciągle nic nie wiemy o
ExternalAPI i czy on faktycznie jest zdalny) wyrobi się z obsłużeniem
takiej ilości zapytań. Wydaje mi się, że bardziej wydajnym rozwiązaniem
było by tu skorzystanie z ThreadPool'a, w którym możesz określić
maksymalną ilość jednoczesnych wywołań, kolejkując pozostałe. Mechanizm
z eventem pozostanie bez zmian.
Gdyby ExternalAPI to był jednak jakiś WebService to możesz skorzystać z
wbudowanych możliwości asynchronicznych zapytań.

Poczytaj o ThreadPoolu tutaj:
http://msdn.microsoft.com/en-us/library/ms973903.aspx

>> A my tutaj to wróżymy z fusów? Napisz więc lepiej, co to API robi,
>> gdzie robi, co Ty robisz i co chcesz osiągnąć.
>
> Ha! Dostałem po uszach 8-) Posypuję głowę popiołem: kod powyżej, cel,
> który chcę osiągnąć jeszcze wyżej. Aha - na ExternalAPI nie mam wpływu:
> jest jakie jest.

Jeśli to jakaś baza danych, to 1000 zapytań może ją zarżnąć (oczywiście
nie musi) :)

tm

Paweł Kierski

unread,
Nov 16, 2009, 3:55:23 AM11/16/09
to
Tomasz Muszyński wrote:
[...]

> Na oko to jest ok. Windows sobie spokojnie poradzi z odpalonym 1000
> wątków, które zasadniczo nic nie robią tylko czekają na odbiór danych.

Czyli jak rozumiem każdy BeginInvoke generuje wątek? To nieco słabe -
z mętnych opisów w książkach dla początkujących wnioskowałem, że to
raczej pula wątków...

> Powinieneś jednak założyć, że zdalny system (ciągle nic nie wiemy o
> ExternalAPI i czy on faktycznie jest zdalny) wyrobi się z obsłużeniem
> takiej ilości zapytań.

Skądinąd wiem, że ma takie ograniczenie, ale w szczególności może to
być dispatcher do wielu zdalnych systemów (np. przypadek pobierania
listy RSSów - praktycznie każdy z innego serwera).

> Wydaje mi się, że bardziej wydajnym rozwiązaniem
> było by tu skorzystanie z ThreadPool'a, w którym możesz określić
> maksymalną ilość jednoczesnych wywołań, kolejkując pozostałe. Mechanizm
> z eventem pozostanie bez zmian.

OK - to rozumiem. Wychodzi mi, że dla jednego zdalnego systemu, który
i tak będzie wąskim gardłem sensowniejszy będzie ThreadPool (doczytam
sobie).

> Gdyby ExternalAPI to był jednak jakiś WebService to możesz skorzystać z
> wbudowanych możliwości asynchronicznych zapytań.

To akurat jest webservice, ale przykryty biblioteką dostępową, na
którą nie mam większego wpływu. Do tego chciałem w ramach nauki
wyprodukować coś w miarę ogólnego 8-)

Będę musiał 8-)

> Jeśli to jakaś baza danych, to 1000 zapytań może ją zarżnąć (oczywiście
> nie musi) :)

To jasne 8-)

Skalowalność tak naprawdę interesowała mnie bardziej pod kątem np.
zupełnie asynchronicznego systemu, w którym tylko niektóre handlery
komunikatów byłyby blokujące. Ale jeśli każde BeginInvoke jest względnie
drogie (tworzy wątek?), to i tak trzeba myśleć o puli. Po za tym mając
pulę miałbym lepszą kontrolę nad systemem. Ale to już tak trochę na boku
pytania.

Dzieki za rozświetlenie sprawy 8-)

--
Paweł Kierski
ne...@pkierski.net

Tomasz Muszyński

unread,
Nov 16, 2009, 4:03:54 PM11/16/09
to
W dniu 09-11-16 09:55, Paweł Kierski pisze:

> Tomasz Muszyński wrote:
> [...]
>> Na oko to jest ok. Windows sobie spokojnie poradzi z odpalonym 1000
>> wątków, które zasadniczo nic nie robią tylko czekają na odbiór danych.
>
> Czyli jak rozumiem każdy BeginInvoke generuje wątek? To nieco słabe -
> z mętnych opisów w książkach dla początkujących wnioskowałem, że to
> raczej pula wątków...

To zależy skąd wywołujesz to BeginInvoke. Z delegate to masz rację, to
jest ThreadPool :) Z Control już nie. Domyślnie ThreadPool wynosi 25
wątków/cpu.

tm

Paweł Kierski

unread,
Nov 17, 2009, 4:35:26 AM11/17/09
to

Dzięki wielkie! To jest cenna wiedza z rodzaju "w książce tego nie
wyczytasz" - przynajmniej nie z pierwszych trzech 8-)

--
Paweł Kierski
ne...@pkierski.net

0 new messages