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
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
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
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
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-)
> Poczytaj o ThreadPoolu tutaj:
> http://msdn.microsoft.com/en-us/library/ms973903.aspx
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
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
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