2010-06-22 3 views
18

Ich mag die Einfachheit der Parallel.For und Parallel.ForEach Erweiterungsmethoden in der TPL. Ich habe mich gefragt, ob es einen Weg gibt, etwas Ähnliches oder sogar die etwas fortgeschritteneren Aufgaben zu nutzen.Gibt es eine Möglichkeit, die Task Parallel Library (TPL) mit SQLDataReader zu verwenden?

Unten ist eine typische Verwendung für den SqlDataReader, und ich fragte mich, ob es möglich war und wenn, wie die While-Schleife unten mit etwas in der TPL zu ersetzen ist. Da der Leser keine feste Anzahl von Iterationen bereitstellen kann, ist die Erweiterungsmethode For nicht möglich, was den Umgang mit Aufgaben, die ich sammeln würde, zulässt. Ich hatte gehofft, dass jemand das bereits angepackt hat und mit ADO.net einige Aufgaben erledigt hat.

Antwort

17

Sie sind fast da. Wickeln Sie den Code, den Sie in einer Funktion mit dieser Signatur geschrieben:

IEnumerable<IDataRecord> MyQuery() 

und dann ersetzen Sie // Do something with Reader Code mit diesem:

yield return reader; 

Jetzt haben Sie etwas haben, das in einem einzigen Thread funktioniert. Wenn Sie die Abfrageergebnisse durchlesen, wird leider jedes Mal ein Verweis auf das Objekt selbe zurückgegeben, und das Objekt mutiert sich für jede Iteration einfach selbst. Dies bedeutet, dass Sie, wenn Sie versuchen, es parallel auszuführen, einige wirklich merkwürdige Ergebnisse erhalten, da parallele Lesevorgänge das in verschiedenen Threads verwendete Objekt mutieren. Sie benötigen Code, um eine Kopie des Datensatzes an Ihre parallele Schleife zu senden.

Zu diesem Zeitpunkt, was ich tun möchte, ist jedoch überspringen die zusätzliche Kopie des Datensatzes und direkt zu einer stark typisierten Klasse. Mehr als das, Ich mag eine allgemeine Methode verwenden, es zu tun:

IEnumerable<T> GetData<T>(Func<IDataRecord, T> factory, string sql, Action<SqlParameterCollection> addParameters) 
{ 
    using (var cn = new SqlConnection("My connection string")) 
    using (var cmd = new SqlCommand(sql, cn)) 
    { 
     addParameters(cmd.Parameters); 

     cn.Open(); 
     using (var rdr = cmd.ExecuteReader()) 
     { 
      while (rdr.Read()) 
      { 
       yield return factory(rdr); 
      } 
     } 
    } 
} 

Angenommen, Ihre Fabrik Methoden erstellen eine Kopie als erwartet, soll dieser Code sicher in einer Parallel.ForEach Schleife zu verwenden.

var UnderPaid = GetData<Employee>(Employee.Create, 
     "SELECT * FROM Employee WHERE AnnualSalary <= @MinSalary", 
     p => { 
      p.Add("@MinSalary", SqlDbType.Int).Value = 50000; 
     }); 
Parallel.ForEach(UnderPaid, e => e.GiveRaise()); 

Wichtiges Update: in diesem Code als ich
Ich bin nicht so sicher Aufruf die Methode würde in etwa so aussehen (eine ein Employee-Klasse mit einer statischen Factory-Methode namens „Create“ vorausgesetzt) war einmal.Ein separater Thread könnte den Leser immer noch mutieren, während ein anderer Thread gerade dabei ist, seine Kopie zu erstellen. Ich könnte eine Sperre um das machen, aber ich bin auch besorgt, dass ein anderer Thread den Leser aktualisieren könnte, nachdem das Original selbst Read() aufgerufen hat, aber bevor es beginnt, die Kopie zu machen. Daher besteht der kritische Abschnitt hier aus der gesamten while-Schleife ... und an diesem Punkt sind Sie wieder zurück zum single-threaded. Ich erwarte, dass es eine Möglichkeit gibt, diesen Code so zu modifizieren, dass er für Multi-Threaded-Szenarien wie erwartet funktioniert, aber es wird mehr Studien benötigen.

+0

ich bin mit dir für die meisten von dem, was Sie gesagt haben, verloren Sie mich ein wenig an der Fabrik. Func factory stimmt nicht mit dem Aufruf überein, wenn er mit yeild return factor (rdr) verwendet wird. Ich denke, Sie haben Func gemeint. Also nicht sicher, was du mit Kopie wie erwartet meinst. Meinst du damit, dass du grundsätzlich vom Leser liest und eine MyDataClass zurückschickst, ähnlich wie Reed in seiner Antwort gesagt hat? –

+0

Sieht auch so aus, als ob Ihr GetData-Aufruf unser Auftrag ist, dass Sie die Factory-Funktion vor der SQL-Zeichenfolge haben. Unabhängig davon, denke ich, bekomme ich es, Ihre Employee.Create ist Ihre Fabrik, die die Arbeit mit dem Leser benötigt. Ich werde eine Weile damit spielen und sehen, wie es läuft. –

+0

Ja, ich meinte Func . Wird das beheben und der Parameter stimmt nicht überein. –

21

Sie werden Schwierigkeiten haben, diese while-Schleife direkt zu ersetzen. SqlDataReader ist keine Thread-sichere Klasse, daher können Sie sie nicht direkt aus mehreren Threads verwenden.

Das gesagt, könnten Sie möglicherweise die Daten verarbeiten, die Sie mit der TPL lesen. Es gibt ein paar Optionen, hier. Am einfachsten ist es, wenn Sie Ihre eigene IEnumerable<T> Implementierung erstellen, die auf dem Leser funktioniert, und eine Klasse oder Struktur zurückgibt, die Ihre Daten enthält. Sie könnten dann verwenden PLINQ oder eine Parallel.ForEach Anweisung Ihre Daten parallel zu verarbeiten:

public IEnumerable<MyDataClass> ReadData() 
{ 
    using (SqlConnection conn = new SqlConnection("myConnString")) 
    using (SqlCommand comm = new SqlCommand("myQuery", conn)) 
    { 
     conn.Open(); 

     SqlDataReader reader = comm.ExecuteReader(); 

     if (reader.HasRows) 
     { 
      while (reader.Read()) 
      { 
       yield return new MyDataClass(... data from reader ...); 
      } 
     } 
    } 
} 

Sobald Sie diese Methode haben, können Sie diese direkt verarbeiten kann, über PLINQ oder TPL:

Parallel.ForEach(this.ReadData(), data => 
{ 
    // Use the data here... 
}); 

Oder:

this.ReadData().AsParallel().ForAll(data => 
{ 
    // Use the data here... 
}); 
Verwandte Themen