2012-05-24 7 views
7

Ich mache einige asynchrone Netzwerk-I/O mit Begin/End-Stil-Methoden. (Es ist eigentlich eine Abfrage gegen Azure Table Storage, aber ich denke nicht, dass das eine Rolle spielt.) Ich habe ein clientseitiges Timeout mit der ThreadPool.RegisterWaitForSingleObject() implementiert. Soweit ich weiß, funktioniert das gut.C#: Verwenden von RegisterWaitForSingleObject, wenn Vorgang zuerst

Da ThreadPool.RegisterWaitForSingleObject() ein WaitHandle als ein Argument dauert, muss ich die E/A-Operation beginnen, dann führen Sie ThreadPool.RegisterWaitForSingleObject(). Es scheint, als ob dadurch die Möglichkeit eingeführt wird, dass die E/A abgeschlossen wird, bevor ich überhaupt die Wartezeit registriere.

Ein vereinfachte Codebeispiel:

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); 
} 

Was ist der richtige Weg, dies zu umgehen? Muss ich mich noch Sorgen machen über Unregister() den AsyncWaitHandle? Wenn ja, gibt es einen ziemlich einfachen Weg, darauf zu warten, dass es gesetzt wird?

+0

Haben Sie versucht, einen 'Thread.Sleep' in der Mitte einzufügen, damit die E/A-Operation abgeschlossen werden kann, und sehen Sie, was passiert? – mellamokb

+0

habe ich nicht. Ich denke, ich habe das nur vielleicht 3-4 Mal gesehen, und dann nur auf stark belasteten Produktionsmaschinen. Ich würde lieber nicht beginnen, Sleep() -Aufrufe in meinen echten Code einzufügen. –

+0

Testen Sie es natürlich in Ihrer Entwicklungsumgebung. Wenn du sagst, dass du das nur 3-4 mal gesehen hast, was hast du gesehen? Eine zufällige unerklärte NullPointerException? – mellamokb

Antwort

4

Ja, Sie und alle anderen haben dieses Problem. Und es spielt keine Rolle, ob der IO synchron ausgeführt wird oder nicht. Zwischen Callback und Aufgabe besteht immer noch ein Wettlauf. Microsoft sollte die Callback-Funktion automatisch zur Verfügung stellen. Das hätte alles gelöst. Na ja, im Nachhinein ist immer 20-20 wie sie sagen.

Sie müssen die Variable so lange lesen, bis sie nicht mehr null ist. Es ist in Ordnung, dies in einer engen Schleife zu tun, da das Rennen so subtil ist, dass die Schleife sich nicht sehr oft dreht.

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

Ich denke, ich sehe, was Sie hier fahren. Ist es wirklich notwendig, die beiden RegisteredWaitHandle-Variablen und das CompareExchange() zu haben? Da die Pointer-Zuweisung eine atomare Operation ist, kann ich nicht einfach die "produzierte" Variable innerhalb des Callbacks untersuchen? –

+0

@breischl: Nun, Sie brauchen eine Speicherbarriere, damit Sie bei jeder Iteration ein "frisches Lesen" der Variablen erhalten. Es wäre wahrscheinlich in Ordnung, den Aufruf "Interlocked.CompareExchange" wegzulassen, da "RegisteredWaitHandle.Unregistered" sicher auch eine Speicherbarriere erzeugt. Aber persönlich bleibe ich bei diesem Muster, weil 'Interlocked.CompareExchange' es explizit macht. –

+0

Wäre nicht "produziert" als "flüchtig" zu deklarieren? –

1

Sie müssen die Registrierung nicht aufheben, wenn die E/A vor dem Timeout abgeschlossen wurde, da dies der Abschluss war, der Ihren Rückruf signalisierte. Tatsächlich scheint es beim Lesen der Dokumente der Unregister-Methode völlig unnötig, sie aufzurufen, da Sie nur einmal ausgeführt werden und Sie sich nicht in einer nicht verwandten Methode abmelden.

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

Wenn eine Callback-Methode durchgeführt wird, wenn Unregister ausführt, wird waitObject nicht, bis die Callback-Methode vervollständigt signalisiert. Insbesondere wenn eine Rückrufmethode Unregister ausführt, wird waitObject erst signalisiert, wenn diese Callback-Methode abgeschlossen ist.

+0

Ich denke, die zitierte Dokumentation sagt, dass es nicht die WaitHandle meiner Operation signalisieren wird. Es scheint nichts über das RegisteredWaitHandle zu sagen, das ich vom ThreadPool bekommen habe. Aber es ist furchtbar verwirrend, also könnte ich mich irren. Die Dokumentation von RegisterWaitForSingleObject() sagt explizit, dass Sie immer Unregister() entfernen sollten, also denke ich, dass ich es irgendwo machen muss. http://msdn.microsoft.com/en-us/library/w9f75h7a.aspx –

+0

@breischl ist QuerySegmentCompleted nur als Rückruf von RegisterWaitForSingleObject aufgerufen? – Slugart

+0

Derzeit ja. –

Verwandte Themen