2012-05-24 13 views
7

Robię niektóre asynchroniczne operacje we/wy sieci przy użyciu metod stylu Początek/Koniec. (W rzeczywistości jest to zapytanie przeciwko Azure Table Storage, ale nie sądzę, aby to miało znaczenie.) Zaimplementowałem limit czasu po stronie klienta, używając ThreadPool.RegisterWaitForSingleObject(). Działa to dobrze, o ile wiem.C#: Używanie RegisterWaitForSingleObject, jeśli operacja zakończy się po raz pierwszy

Ponieważ ThreadPool.RegisterWaitForSingleObject() pobiera WaitHandle jako argument, muszę rozpocząć operację I/O, a następnie wykonać ThreadPool.RegisterWaitForSingleObject(). Wydaje się, że to wprowadza możliwość, że I/O kończy się, zanim jeszcze zarejestruję czekanie.

Uproszczony przykładowy kod:

private void RunQuery(QueryState queryState) 
{ 
    //Start I/O operation 
    IAsyncResult asyncResult = queryState.Query.BeginExecuteSegmented(NoopAsyncCallback, queryState); 

    //What if the I/O operation completes here? 

    queryState.TimeoutWaitHandle = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, QuerySegmentCompleted, asyncResult, queryTimeout, true); 
} 

private void QuerySegmentCompleted(object opState, bool timedOut){ 
    IAsyncResult asyncResult = opState as IAsyncResult; 
    QueryState state = asyncResult.AsyncState as QueryState; 

    //If the I/O completed quickly, could TimeoutWaitHandle could be null here? 
    //If so, what do I do about that? 
    state.TimeoutWaitHandle.Unregister(asyncResult.AsyncWaitHandle); 
} 

Jaki jest właściwy sposób, aby sobie z tym poradzić? Czy nadal muszę się martwić o Unregister() "AsyncWaitHandle? Jeśli tak, czy istnieje dość łatwy sposób, aby czekać na jego ustawienie?

+0

Czy próbowałeś wstawić 'Thread.Sleep' w środku, aby dać czas na zakończenie operacji I/O i zobaczyć, co się stanie? – mellamokb

+0

Nie mam. Myślę, że widziałem to tylko 3-4 razy, a potem tylko na mocno obciążonych maszynach produkcyjnych. Wolałbym nie dodawać wywołań Sleep() do mojego prawdziwego kodu. –

+0

Sprawdź oczywiście w swoim środowisku programistycznym. Kiedy mówisz, że widziałeś to zdarzenie tylko 3-4 razy, co się stało? Losowy niewyjaśniony wyjątek NullPointerException? – mellamokb

Odpowiedz

4

Tak, ty i wszyscy inni masz ten problem. I nie ma znaczenia, czy IO zakończyło się synchronicznie, czy też nie. Nadal trwa wyścig między wywołaniem zwrotnym a zleceniem. Firma Microsoft powinna udostępnić do tej funkcji oddzwaniania numer RegisteredWaitHandle. To rozwiązałoby wszystko. No cóż, po fakcie jest zawsze 20-20, jak to mówią.

Co należy zrobić, należy czytać zmienną RegisteredWaitHandle, dopóki nie będzie już pusta. Można to zrobić w ciasnej pętli, ponieważ wyścig jest na tyle subtelny, że pętla nie obraca się zbyt wiele razy.

private void RunQuery(QueryState queryState) 
{ 
    // Start the operation. 
    var asyncResult = queryState.Query.BeginExecuteSegmented(NoopAsyncCallback, queryState); 

    // Register a callback. 
    RegisteredWaitHandle shared = null; 
    RegisteredWaitHandle produced = ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, 
    (state, timedout) => 
    { 
     var asyncResult = opState as IAsyncResult; 
     var state = asyncResult.AsyncState as QueryState; 
     while (true) 
     { 
     // Keep reading until the value is no longer null. 
     RegisteredWaitHandle consumed = Interlocked.CompareExchange(ref shared, null, null); 
     if (consumed != null) 
     { 
      consumed.Unregister(asyncResult.AsyncWaitHandle); 
      break; 
     } 
     } 
    }, asyncResult, queryTimeout, true); 

    // Publish the RegisteredWaitHandle so that the callback can see it. 
    Interlocked.CompareExchange(ref shared, produced, null); 
} 
+0

Chyba widzę, co tu robisz. Czy naprawdę konieczne jest posiadanie dwóch zmiennych RegisteredWaitHandle i CompareExchange()? Ponieważ przypisanie wskaźnika jest operacją atomową, czy nie mogę po prostu sprawdzić zmiennej 'wyprodukowanej 'wewnątrz wywołania zwrotnego? –

+0

@breischl: Cóż, potrzebujesz bariery pamięci, aby uzyskać "nowy odczyt" zmiennej w każdej iteracji. Prawdopodobnie będziesz w stanie pominąć wywołanie 'Interlocked.CompareExchange', ponieważ jestem pewien, że' RegisteredWaitHandle.Unregistered' również generuje barierę pamięci. Ale osobiście trzymałbym się tego wzorca, ponieważ "Interlocked.CompareExchange" czyni to wyraźnym. –

+0

Czy deklarowanie 'wyprodukowanego' jako' volatile' nie ma takiego samego efektu? –

1

Nie musisz wyrejestrowywać się, jeśli operacje wejścia/wyjścia zostały zakończone przed upływem limitu czasu, ponieważ było to ukończenie, które zasygnalizowało twoje oddzwanianie. W rzeczywistości po przeczytaniu dokumentacji metody wyrejestrowania wydaje się całkowicie niepotrzebne, aby nazwać ją, jak wykonujesz tylko raz i nie wyrejestrujesz się w niezwiązanej ze sobą metodzie.

http://msdn.microsoft.com/en-us/library/system.threading.registeredwaithandle.unregister.aspx

Jeśli metoda wywołania zwrotnego jest w toku gdy Wyrejestruj wykonuje, waitObject nie jest sygnalizowane aż metoda zwrotna kończy. W szczególności, jeśli metoda wywołania zwrotnego wykonuje wyrejestrowanie, obiekt waitObject nie zostanie zasygnalizowany, dopóki ta metoda wywołania zwrotnego nie zostanie zakończona.

+0

Myślę, że cytowana dokumentacja mówi, że nie będzie sygnalizować WaitHandle mojej operacji. Wygląda na to, że nic nie mówi o RegisteredWaitHandle, które dostałem z ThreadPool. Ale to jest strasznie zagmatwane, więc mogę się mylić. Dokumentacja RegisterWaitForSingleObject() wyraźnie mówi, że zawsze powinieneś wyrejestrować(), więc myślę, że muszę to gdzieś zrobić. http://msdn.microsoft.com/en-us/library/w9f75h7a.aspx –

+0

@breischl to QuerySegmentCompleted wywoływany tylko jako oddzwonienie z RegisterWaitForSingleObject? – Slugart

+0

Obecnie, tak. –

Powiązane problemy