2017-05-17 2 views
1

Ich verwende eine Website, die Produktfeeds aus einer Vielzahl von Shops importiert. Diese Feeds können ziemlich groß sein, einige sind bis zu 1 GB. Derzeit importiere ich diese durch die Importfunktion in einer Schleife aufrufen:QueueUserWorkItem mit WaitCallback Abrufen des Rückgabewerts

For i As Integer = 0 To dtAllFeeds.Rows.Count - 1 

    iImported = ImportFeed(dtAllFeeds(i).id) 
    totalProductsImported += iImported 
    lblStatus.Text += "FeedId: " + dtAllFeeds(i).id.ToString + "Items Imported: " + iImported.ToString 

    If iImported = 0 Then 
     MailFunctions.NotifyAdmin("feed error: dtAllFeeds(i).id.ToString) 
    End If 
Next i 

lblStatus.Text += "Total imported: " + totalProductsImported.ToString 

Dies funktioniert, aber wie die Größe oder Anzahl von RSS-Feeds erhöhen, so dass die Zeit tut, sie zu verarbeiten. Also habe ich nicht so elegant erhöht nur das Timeout:

Protected Sub Page_Init(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Init 
    _timeOut = Server.ScriptTimeout 
    Server.ScriptTimeout = 36000 '10 hours 
End Sub 

Nun möchte ich das nächste abzuschließen für jede Aufgabe, um diese Aufgaben auszuführen, ohne warten vor dem Start, so habe ich versucht, das Setup als here beschrieben, zuerst mit einer Testfunktion TestMultiThread:

Protected Function TestMultiThread(ByVal Id As Integer, ByVal s As String) As Integer 
    LogError("s = " + s) 
    For i As Integer = 0 To (Id * 10000) 
    Next i 
    LogError(Id.ToString + " completed") 
    Return Id * 10000 
End Function 

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 
Dim numThreads = 20 
Dim toProcess = numThreads 
Dim resetEvent = New ManualResetEvent(False) 
Dim i As Integer 
For i = 1 To numThreads 
    ThreadPool.QueueUserWorkItem(New WaitCallback(Sub(state As Object) 
                 TestMultiThread(i, toProcess.ToString) 
                 If Interlocked.Decrement(toProcess) = 0 Then 
                  resetEvent.[Set]() 
                 End If 
                End Sub), Nothing) 
Next i 

resetEvent.WaitOne() 
End Sub 

ich werde dann diese Fehler protokolliert:

s = 20 
s = 20 
s = 20 
21 completed 
21 completed 
21 completed 
s = 19 
s = 18 
s = 17 
21 completed 
21 completed 
s = 16 
21 completed 
s = 15 
21 completed 
s = 14 
21 completed 
s = 13 
21 completed 
s = 12 
21 completed 
s = 11 
21 completed 
s = 10 
21 completed 
s = 9 
21 completed 
s = 8 
21 completed 
s = 7 
21 completed 
s = 6 
21 completed 
s = 5 
21 completed 
s = 4 
21 completed 
s = 3 
21 completed 
21 completed 

ich nicht understa nd diese Reihenfolge der Protokollierung, wie ich sehe nicht s = <value> für 20 eindeutige Werte (aber s=20 sogar 3 mal hintereinander am Anfang und fehlt s=2 und s=1? Und warum ist i immer 21 in Funktion TestMultiThread?

+0

Es heißt "Capturing the Loop var", siehe Antwort unten und [hier (C#)] (http://stackoverflow.com/q/43979917/). –

+0

Aber die eigentliche Frage sollte gewesen sein, "wie Feeds parallel zu verarbeiten", die guten Antworten hätten QueueUserWorkItem nicht beteiligt. –

+0

@Henk Ich habe einen besseren Weg gefunden, um mit der Situation umzugehen, nachdem ich erklärt habe, warum die Verwendung von QUWI zu unerwarteten Ergebnissen geführt hat. – ejohnson

Antwort

2

Sie haben mehrere Probleme. Zuallererst kann ich es nicht einmal mit For i = 0 To (i * 10000) in TestMultiThread kompilieren, weil Sie auch i als Parametername verwenden. Die zweite seltsame Eigenschaft ist, dass Sie den Loop-Iterator i an TestMultiThread übergeben. Dies ist ein modifizierter Abschluss - Sie erfassen die Variable selbst und nicht ihren Wert. Zu dem Zeitpunkt, zu dem jeder Threadpool-Delegat ausgeführt wird, ist der Wert i 21, der nach jeder Iteration des Schleifenkörpers erhöht wurde. Um dieses Problem zu lösen, kopieren Sie i in eine lokale Variable innerhalb des Schleifenkörpers und übergeben Sie stattdessen diese lokale Variable an TestMultiThread.

Schließlich, da dies im Kontext von ASP.NET getan wird, beachten Sie, dass das Aufsprühen einer Menge neuer Threads den ASP.NET-Thread-Pool von Threads ausräumen kann, die es verwenden kann, um eingehende Anforderungen zu verarbeiten. Stephen Cleary explains:

  • Die Anforderung startet die Verarbeitung auf einem ASP.NET Gewinde.
  • Task.Run startet eine Aufgabe im Thread-Pool, um die Berechnungen auszuführen. Der ASP.NET-Thread
    Pool muss mit (unerwarteterweise) Verlust eines seiner Threads für
    die Dauer dieser Anfrage beschäftigen.
  • Der ursprüngliche Anfrage-Thread wird an den ASP.NET-Thread-Pool zurückgegeben.
  • Wenn die Berechnung abgeschlossen ist, vervollständigt der Thread
    die Anforderung und wird an den ASP.NET-Thread
    Pool zurückgegeben. Der ASP.NET-Thread-Pool muss sich (unerwartet) mit einem anderen Thread befassen.

ThreadPool.QueueUserWorkItem in Ihrem Fall ist Task.Run in seinem Beispiel analog - sie schafft einen Hintergrund-Thread. Wenn Sie in ASP.NET "fire-and-forget" durchführen möchten, verwenden Sie die folgenden Optionen: HostingEnvironment.QueueBackgroundWorkItem, as Cleary suggests. Wenn Sie nach dem Importieren wirklich etwas mit den Feeds machen müssen, sollten Sie die Vorteile von asynchronous programming nutzen, um jeden Import zu starten, bevor alle gleichzeitig erwartet werden (ich nehme an, dass Sie APIs aufrufen - eine natürlich asynchrone Operation - da Sie es sind Importieren der Feeds "aus einer Vielzahl von Shops").

+0

Danke. Es sieht so aus, als wäre das 'QueueBackgroundWorkItem' der schnellste Gewinn. Nicht sicher, was Sie mit "Wenn Sie wirklich etwas mit den Feeds tun müssen, nachdem Sie sie importiert haben", brauche ich nur den Rückgabewert (Anzahl der Elemente importiert) von der Funktion, die einen Feed importiert ... ist "QueueBackgroundWorkItem 'immer noch meine beste Wette oder sollte ich in Async-Programmierung schauen? Danke noch einmal. – Flo

+0

Ich habe versucht, zwischen "fire-and-forget" -artigen Hintergrundarbeiten und normalen Arbeiten zu unterscheiden, die Sie gerade parallelisieren möchten. Es klingt so, als sollten Sie eine Aufgabe erstellen, um den Feed von jedem Shop zu importieren und dann 'Task.WhenAll (tasks) 'zu erwarten. – ejohnson

Verwandte Themen