it-swarm.com.de

C # -Anwendung mit mehreren Threads mit SQL Server-Datenbankaufrufen

Ich habe eine SQL Server-Datenbank mit 500.000 Datensätzen in Tabelle main. Es gibt auch drei weitere Tabellen mit den Namen child1, child2 und child3. Die vielen bis vielen Beziehungen zwischen child1, child2, child3 und main werden über die drei Beziehungstabellen implementiert: main_child1_relationship, main_child2_relationship und main_child3_relationship. Ich muss die Datensätze in main lesen, main aktualisieren und auch neue Zeilen in die Beziehungstabellen einfügen sowie neue Datensätze in die untergeordneten Tabellen einfügen. Die Datensätze in den untergeordneten Tabellen haben Eindeutigkeitsbeschränkungen. Der Pseudo-Code für die tatsächliche Berechnung (CalculateDetails) würde etwa so aussehen:

for each record in main
{
   find its child1 like qualities
   for each one of its child1 qualities
   {
      find the record in child1 that matches that quality
      if found
      {
          add a record to main_child1_relationship to connect the two records
      }
      else
      {
          create a new record in child1 for the quality mentioned
          add a record to main_child1_relationship to connect the two records
      }
   }
   ...repeat the above for child2
   ...repeat the above for child3 
}

Dies funktioniert gut als Single-Threaded-App. Es ist aber zu langsam. Die Verarbeitung in C # ist ziemlich hoch und dauert zu lange. Ich möchte daraus eine Multithread-App machen.

Was ist der beste Weg, dies zu tun? Wir verwenden Linq to Sql.

Bisher bestand mein Ansatz darin, für jeden Datensatzstapel aus DataContext ein neues main-Objekt zu erstellen und es mit ThreadPool.QueueUserWorkItem zu verarbeiten. Diese Batches treten jedoch auf die Zehen des anderen, weil ein Thread einen Datensatz hinzufügt und dann versucht der nächste Thread, den gleichen hinzuzufügen, und ... Ich bekomme alle Arten von interessanten SQL Server-Sperren.

Hier ist der Code:

    int skip = 0;
    List<int> thisBatch;
    Queue<List<int>> allBatches = new Queue<List<int>>();
    do
    {
        thisBatch = allIds
                .Skip(skip)
                .Take(numberOfRecordsToPullFromDBAtATime).ToList();
        allBatches.Enqueue(thisBatch);
        skip += numberOfRecordsToPullFromDBAtATime;

    } while (thisBatch.Count() > 0);

    while (allBatches.Count() > 0)
    {
        RRDataContext rrdc = new RRDataContext();

        var currentBatch = allBatches.Dequeue();
        lock (locker)  
        {
            runningTasks++;
        }
        System.Threading.ThreadPool.QueueUserWorkItem(x =>
                    ProcessBatch(currentBatch, rrdc));

        lock (locker) 
        {
            while (runningTasks > MAX_NUMBER_OF_THREADS)
            {
                 Monitor.Wait(locker);
                 UpdateGUI();
            }
        }
    }

Und hier ist ProcessBatch:

    private static void ProcessBatch( 
        List<int> currentBatch, RRDataContext rrdc)
    {
        var topRecords = GetTopRecords(rrdc, currentBatch);
        CalculateDetails(rrdc, topRecords);
        rrdc.Dispose();

        lock (locker)
        {
            runningTasks--;
            Monitor.Pulse(locker);
        };
    }

Und 

    private static List<Record> GetTopRecords(RecipeRelationshipsDataContext rrdc, 
                                              List<int> thisBatch)
    {
        List<Record> topRecords;

        topRecords = rrdc.Records
                    .Where(x => thisBatch.Contains(x.Id))
                    .OrderBy(x => x.OrderByMe).ToList();
        return topRecords;
    }

CalculateDetails lässt sich am besten durch den Pseudocode oben erklären. 

Ich denke, es muss einen besseren Weg geben, dies zu tun. Bitte helfen Danke vielmals!

22
Barka

Hier ist meine Meinung zu dem Problem:

  • Wenn mehrere Threads zum Einfügen/Aktualisieren/Abfragen von Daten in SQL Server oder einer beliebigen Datenbank verwendet werden, sind Deadlocks eine Tatsache. Sie müssen davon ausgehen, dass sie auftreten werden und sie entsprechend behandeln.

  • Das heißt nicht, dass wir nicht versuchen sollten, das Auftreten von Deadlocks zu begrenzen. Es ist jedoch leicht, sich mit den grundlegenden Ursachen von Deadlocks vertraut zu machen und Maßnahmen zu ergreifen, um sie zu verhindern, aber SQL Server wird Sie immer überraschen :-)

Ein Grund für Deadlocks:

  • Zu viele Threads - Versuchen Sie, die Anzahl der Threads auf ein Minimum zu beschränken, aber natürlich möchten wir mehr Threads für maximale Leistung.

  • Nicht genügend Indizes. Wenn selects und Updates nicht selektiv genug sind, entfernt SQL größere Bereichssperren, als es fehlerfrei ist. Versuchen Sie, geeignete Indizes anzugeben.

  • Zu viele Indizes. Die Aktualisierung von Indizes führt zu Deadlocks. Versuchen Sie daher, die Indizes auf das erforderliche Minimum zu reduzieren.

  • Transaktionsisolationsstufe zu hoch. Der Standardwert Isolationsstufe ist bei Verwendung von .NET 'Serializable', während der Standardwert für SQL Server 'Read Committed' ist. Das Reduzieren des Isolationsniveaus kann (wenn zutreffend natürlich) sehr helfen.

So könnte ich Ihr Problem angehen:

  • Ich würde meine eigene Threading-Lösung nicht rollen, ich würde die TaskParallel-Bibliothek verwenden. Meine Hauptmethode würde ungefähr so ​​aussehen:

    using (var dc = new TestDataContext())
    {
        // Get all the ids of interest.
        // I assume you mark successfully updated rows in some way
        // in the update transaction.
        List<int> ids = dc.TestItems.Where(...).Select(item => item.Id).ToList();
    
        var problematicIds = new List<ErrorType>();
    
        // Either allow the TaskParallel library to select what it considers
        // as the optimum degree of parallelism by omitting the 
        // ParallelOptions parameter, or specify what you want.
        Parallel.ForEach(ids, new ParallelOptions {MaxDegreeOfParallelism = 8},
                            id => CalculateDetails(id, problematicIds));
    }
    
  • Führen Sie die CalculateDetails-Methode mit Wiederholungsversuchen für Deadlock-Fehler aus

    private static void CalculateDetails(int id, List<ErrorType> problematicIds)
    {
        try
        {
            // Handle deadlocks
            DeadlockRetryHelper.Execute(() => CalculateDetails(id));
        }
        catch (Exception e)
        {
            // Too many deadlock retries (or other exception). 
            // Record so we can diagnose problem or retry later
            problematicIds.Add(new ErrorType(id, e));
        }
    }
    
  • Die CalculateDetails-Kernmethode

    private static void CalculateDetails(int id)
    {
        // Creating a new DeviceContext is not expensive.
        // No need to create outside of this method.
        using (var dc = new TestDataContext())
        {
            // TODO: adjust IsolationLevel to minimize deadlocks
            // If you don't need to change the isolation level 
            // then you can remove the TransactionScope altogether
            using (var scope = new TransactionScope(
                TransactionScopeOption.Required,
                new TransactionOptions {IsolationLevel = IsolationLevel.Serializable}))
            {
                TestItem item = dc.TestItems.Single(i => i.Id == id);
    
                // work done here
    
                dc.SubmitChanges();
                scope.Complete();
            }
        }
    }
    
  • Und natürlich meine Implementierung eines Deadlock-Neuversuchshelfers

    public static class DeadlockRetryHelper
    {
        private const int MaxRetries = 4;
        private const int SqlDeadlock = 1205;
    
        public static void Execute(Action action, int maxRetries = MaxRetries)
        {
            if (HasAmbientTransaction())
            {
                // Deadlock blows out containing transaction
                // so no point retrying if already in tx.
                action();
            }
    
            int retries = 0;
    
            while (retries < maxRetries)
            {
                try
                {
                    action();
                    return;
                }
                catch (Exception e)
                {
                    if (IsSqlDeadlock(e))
                    {
                        retries++;
                        // Delay subsequent retries - not sure if this helps or not
                        Thread.Sleep(100 * retries);
                    }
                    else
                    {
                        throw;
                    }
                }
            }
    
            action();
        }
    
        private static bool HasAmbientTransaction()
        {
            return Transaction.Current != null;
        }
    
        private static bool IsSqlDeadlock(Exception exception)
        {
            if (exception == null)
            {
                return false;
            }
    
            var sqlException = exception as SqlException;
    
            if (sqlException != null && sqlException.Number == SqlDeadlock)
            {
                return true;
            }
    
            if (exception.InnerException != null)
            {
                return IsSqlDeadlock(exception.InnerException);
            }
    
            return false;
        }
    }
    
  • Eine weitere Möglichkeit ist die Verwendung einer Partitionierungsstrategie

Wenn Ihre Tabellen natürlich in mehrere unterschiedliche Datensätze partitioniert werden können, können Sie entweder Partitionierte SQL Server-Tabellen und -Indizes verwenden, oder Sie können manuell aufteilen Ihre vorhandenen Tabellen in mehrere Datensätze unterteilen Tische. Ich würde empfehlen, die Partitionierung von SQL Server zu verwenden, da die zweite Option unordentlich wäre. Auch die integrierte Partitionierung ist nur in SQL Enterprise Edition verfügbar.

Wenn Partitionierung für Sie möglich ist, können Sie ein Partitionsschema wählen, in dem Ihre Daten gebrochen wurden, beispielsweise 8 verschiedene Sätze. Jetzt könnten Sie Ihren ursprünglichen Single-Thread-Code verwenden, aber 8 Threads haben jeweils eine separate Partition. Jetzt gibt es keine (oder zumindest eine minimale Anzahl) Deadlocks.

Ich hoffe das ergibt Sinn. 

46
Phil

Überblick

Der Grund für Ihr Problem ist, dass der L2S-DataContext wie der ObjectContext von Entity Framework nicht threadsicher ist. Wie in diesem MSDN-Forenaustausch erläutert - ist die Unterstützung für asynchrone Vorgänge in .NET ORM-Lösungen ab .NET 4.0 noch ausstehend. Sie müssen Ihre eigene Lösung rollen, was, wie Sie entdeckt haben, nicht immer einfach ist, wenn Ihr Framework Singlethreaded hat.

Ich möchte die Gelegenheit nutzen, um zu bemerken, dass L2S auf ADO.NET basiert, das den asynchronen Betrieb vollständig unterstützt. Persönlich würde ich es vorziehen, direkt mit dieser unteren Schicht zu arbeiten und die SQL selbst zu schreiben, nur um sicherzustellen, dass dies der Fall ist Ich habe voll verstanden, was über das Netzwerk abläuft.

SQL Server-Lösung?

Davon abgesehen muss ich fragen - muss dies eine C # -Lösung sein? Wenn Sie Ihre Lösung aus einer Menge von Einfügungs-/Aktualisierungsanweisungen zusammenstellen können, können Sie die SQL direkt übermitteln und Ihre Threading- und Leistungsprobleme verschwinden. * Ihre Probleme beziehen sich meines Erachtens nicht auf die eigentlichen Datentransformationen gemacht, aber zentriert, um sie von .NET aus performant zu machen. Wenn .NET aus der Gleichung entfernt wird, wird Ihre Aufgabe einfacher. Schließlich ist die beste Lösung oft diejenige, bei der Sie die kleinste Menge Code schreiben, oder? ;)

Selbst wenn Ihre Aktualisierungs-/Einfügelogik nicht streng set-relational ausgedrückt werden kann, verfügt SQL Server über einen integrierten Mechanismus zum Durchlaufen von Datensätzen und zum Durchführen von Logik. Während dies für viele Anwendungsfälle zu Unrecht der Fall ist, können Cursor dies tun Tatsache, für Ihre Aufgabe angemessen sein.

Wenn dies eine Aufgabe ist, die immer wieder wiederholt werden muss, können Sie davon profitieren, wenn Sie sie als gespeicherte Prozedur codieren.

* Natürlich bringt lang andauerndes SQL seine eigenen Probleme mit sich, wie Sperreneskalation und Indexnutzung, mit denen Sie konfrontiert werden müssen.

C # -Lösung

Es kann natürlich sein, dass dies in SQL nicht möglich ist - die Entscheidungen Ihres Codes hängen zum Beispiel von Daten ab, die aus anderen Ländern stammen, oder möglicherweise hat Ihr Projekt eine strikte "no-SQL-allowed" -Konvention. Sie erwähnen einige typische Multithreading-Fehler, aber ohne Ihren Code zu sehen, kann ich nicht besonders hilfreich sein.

Dies ist natürlich in C # möglich, aber Sie müssen sich damit auseinandersetzen, dass es für jeden Anruf, den Sie tätigen, eine feste Latenzzeit gibt. Sie können die Auswirkungen der Netzwerklatenz verringern, indem Sie gepoolte Verbindungen verwenden, mehrere aktive Ergebnissätze aktivieren und die asynchronen Begin/End-Methoden zum Ausführen Ihrer Abfragen verwenden. Trotz all dieser Faktoren müssen Sie immer noch akzeptieren, dass der Versand von Daten von SQL Server an Ihre Anwendung mit Kosten verbunden ist.

Eine der besten Möglichkeiten, um zu verhindern, dass Ihr Code sich selbst überläuft, besteht darin, zu vermeiden, dass veränderliche Daten so weit wie möglich zwischen Threads geteilt werden. Dies würde bedeuten, dass derselbe DataContext nicht für mehrere Threads freigegeben wird. Der nächstbeste Ansatz besteht darin, kritische Codeabschnitte zu sperren, die die gemeinsam genutzten Daten berühren - lock - Blöcke um den gesamten DataContext-Zugriff, vom ersten Lesevorgang bis zum endgültigen Schreibvorgang. Dieser Ansatz könnte die Vorteile des Multithreading vollständig vermeiden. Sie können Ihre Verriegelung wahrscheinlich feinkörniger machen, aber seien Sie gewarnt, dass dies ein Weg des Schmerzes ist.

Weitaus besser ist es, Ihre Operationen vollständig voneinander zu trennen. Wenn Sie Ihre Logik in "Haupt" -Datensätze unterteilen können, ist dies ideal - das heißt, solange zwischen den verschiedenen untergeordneten Tabellen keine Beziehungen bestehen und solange ein Datensatz in "Haupt" keine Auswirkungen hat Zum anderen können Sie Ihre Operationen wie folgt auf mehrere Threads aufteilen:

private IList<int> GetMainIds()
{
    using (var context = new MyDataContext())
        return context.Main.Select(m => m.Id).ToList();
}

private void FixUpSingleRecord(int mainRecordId)
{
    using (var localContext = new MyDataContext())
    {
        var main = localContext.Main.FirstOrDefault(m => m.Id == mainRecordId);

        if (main == null)
            return;

        foreach (var childOneQuality in main.ChildOneQualities)
        {
            // If child one is not found, create it
            // Create the relationship if needed
        }

        // Repeat for ChildTwo and ChildThree

        localContext.SaveChanges();
    }
}

public void FixUpMain()
{
    var ids = GetMainIds();
    foreach (var id in ids)
    {
        var localId = id; // Avoid closing over an iteration member
        ThreadPool.QueueUserWorkItem(delegate { FixUpSingleRecord(id) });
    }
}

Natürlich ist dies ebenso ein Spielzeugbeispiel wie der Pseudocode in Ihrer Frage, aber Sie werden hoffentlich darüber nachdenken, wie Sie Ihre Aufgaben so gestalten, dass zwischen ihnen kein (oder minimaler) gemeinsamer Zustand besteht. Ich denke, das ist der Schlüssel zu einer korrekten C # -Lösung.

BEARBEITEN Auf Updates und Kommentare reagieren

Wenn Sie Probleme mit der Datenkonsistenz feststellen, empfiehlt es sich, die Transaktionssemantik durchzusetzen. Dazu können Sie einen System.Transactions.TransactionScope verwenden (einen Verweis auf System.Transactions hinzufügen). Alternativ können Sie dies auch auf ADO.NET-Ebene tun, indem Sie auf die innere Verbindung zugreifen und BeginTransaction (oder wie auch immer die DataConnection-Methode aufgerufen wird) aufrufen.

Sie erwähnen auch Deadlocks. Dass Sie gegen SQL Server-Deadlocks kämpfen, zeigt an, dass die eigentlichen SQL-Abfragen sich gegenseitig auf die Zehen treten. Ohne zu wissen, was tatsächlich über die Leitung geschickt wird, ist es schwierig, im Detail zu sagen, was passiert und wie man es beheben kann. Es genügt zu sagen, dass SQL-Deadlocks aus SQL-Abfragen und nicht notwendigerweise aus C # -Threading-Konstrukten resultieren - Sie müssen untersuchen, was genau über den Draht geht. Mein Bauch sagt mir, dass, wenn jeder Hauptdatensatz wirklich unabhängig von den anderen ist, Zeilen- und Tabellensperren nicht erforderlich sein sollten und dass Linq to SQL wahrscheinlich der Täter ist.Sie können einen Speicherauszug des von L2S in Ihrem Code ausgegebenen rohen SQL erhalten, indem Sie die DataContext.Log -Eigenschaft auf etwas festlegen, z. Console.Out. Obwohl ich es noch nie persönlich verwendet habe, ist mir klar, dass das LINQPad L2S-Funktionen bietet und Sie möglicherweise auch dort SQL nutzen können.

Mit SQL Server Management Studio erhalten Sie den Rest des Weges. Mit dem Aktivitätsmonitor können Sie in Echtzeit auf die Eskalation von Sperren achten. Mit dem Query Analyzer erhalten Sie einen Überblick darüber, wie SQL Server Ihre Abfragen genau ausführt. Mit diesen sollten Sie in der Lage sein, eine gute Vorstellung davon zu bekommen, was Ihr Code serverseitig tut, und wie Sie ihn beheben können.

SQL Server Management Studio will get you the rest of the way there - using the Activity Monitor, you can watch for lock escalation in real time. Using the Query Analyzer, you can get a view of exactly how SQL Server will execute your queries. With those, you should be able to get a good notion of what your code is doing server-side, and in turn how to go about fixing it.

5
Ben

Ich würde auch empfehlen, die gesamte XML-Verarbeitung in den SQL-Server zu verschieben. Nicht nur alle Ihre Deadlocks werden verschwinden, sondern Sie werden einen solchen Leistungszuwachs bemerken, dass Sie nie wieder zurückkehren möchten.

Dies wird am besten durch ein Beispiel erklärt. In diesem Beispiel gehe ich davon aus, dass das XML-Blob bereits in Ihre Haupttabelle aufgenommen wird (ich nenne es Schrank). Ich werde folgendes Schema annehmen:

CREATE TABLE closet (id int PRIMARY KEY, xmldoc ntext) 
CREATE TABLE shoe(id int PRIMARY KEY IDENTITY, color nvarchar(20))
CREATE TABLE closet_shoe_relationship (
    closet_id int REFERENCES closet(id),
    shoe_id int REFERENCES shoe(id)
)

Und ich erwarte, dass Ihre Daten (nur Haupttabelle) anfangs so aussehen:

INSERT INTO closet(id, xmldoc) VALUES (1, '<ROOT><shoe><color>blue</color></shoe></ROOT>')
INSERT INTO closet(id, xmldoc) VALUES (2, '<ROOT><shoe><color>red</color></shoe></ROOT>')

Dann ist deine ganze Aufgabe so einfach wie folgt:

INSERT INTO shoe(color) SELECT DISTINCT CAST(CAST(xmldoc AS xml).query('//shoe/color/text()') AS nvarchar) AS color from closet
INSERT INTO closet_shoe_relationship(closet_id, shoe_id) SELECT closet.id, shoe.id FROM shoe JOIN closet ON CAST(CAST(closet.xmldoc AS xml).query('//shoe/color/text()') AS nvarchar) = shoe.color

Da Sie jedoch viele ähnliche Verarbeitungsvorgänge durchführen, können Sie Ihr Leben einfacher machen, indem Sie Ihren Haupt-Blob als XML-Typ deklarieren und dies weiter vereinfachen:

INSERT INTO shoe(color)
    SELECT DISTINCT CAST(xmldoc.query('//shoe/color/text()') AS nvarchar)
    FROM closet
INSERT INTO closet_shoe_relationship(closet_id, shoe_id)
    SELECT closet.id, shoe.id
    FROM shoe JOIN closet
        ON CAST(xmldoc.query('//shoe/color/text()') AS nvarchar) = shoe.color

Es sind zusätzliche Leistungsoptimierungen möglich, z. B. das wiederholte Aufrufen von Xpath-Ergebnissen in einer temporären oder permanenten Tabelle oder das Konvertieren der anfänglichen Grundgesamtheit der Haupttabelle in eine BULK INSERT. Ich erwarte jedoch nicht, dass Sie diese wirklich benötigen, um erfolgreich zu sein .

2
Jirka Hanika

sQL Server-Deadlocks sind in dieser Art von Szenario normal und zu erwarten - MS empfiehlt, diese sollten auf der Anwendungsseite gehandhabt werden und nicht auf der Db-Seite.

Wenn Sie jedoch sicherstellen müssen, dass eine gespeicherte Prozedur nur einmal aufgerufen wird, können Sie eine SQL-Mutex-Sperre mithilfe von sp_getapplock verwenden. Hier ist ein Beispiel, wie Sie dies implementieren können

BEGIN TRAN
DECLARE @mutex_result int;
EXEC @mutex_result = sp_getapplock @Resource = 'CheckSetFileTransferLock',
 @LockMode = 'Exclusive';

IF ( @mutex_result < 0)
BEGIN
    ROLLBACK TRAN

END

-- do some stuff

EXEC @mutex_result = sp_releaseapplock @Resource = 'CheckSetFileTransferLock'
COMMIT TRAN  
1
Johnv2020

Dieses Problem kann mit Hilfe eines LimitedConcurrencyLevelTaskScheduler gelöst werden. 

public class InOutMessagesController
{
    private static LimitedConcurrencyLevelTaskScheduler scheduler = new LimitedConcurrencyLevelTaskScheduler(1);
    private TaskFactory taskFactory = new TaskFactory(scheduler);
    private TaskFactory<MyTask<Object[]>> taskFactoryWithResult = new TaskFactory<MyTask<Object[]>>(scheduler);
    private ConcurrentBag<Task> tasks = new ConcurrentBag<Task>();
    private ConcurrentBag<MyTask<Object[]>> tasksWithResult = new ConcurrentBag<MyTask<Object[]>>();
    private ConcurrentBag<int> endedTaskIds = new ConcurrentBag<int>();
    private ConcurrentBag<int> endedTaskWithResultIds = new ConcurrentBag<int>();
    private Task TaskForgetEndedTasks;
    private static object taskForgetLocker = new object();


    #region Conveyor
    private async void AddTaskVoidToQueue(Task task)
    {
        try
        {
            tasks.Add(task);

            await taskFactory.StartNew(() => task.Start());

            if (TaskForgetEndedTasks == null)
            {
                ForgetTasks();
            }
        }
        catch (Exception ex)
        {
            NLogger.Error(ex);
        }
    }

    private async Task<Object[]> AddTaskWithResultToQueue(MyTask<Object[]> task)
    {
        ForgetTasks();

        tasksWithResult.Add(task);

        return await taskFactoryWithResult.StartNew(() => { task.Start(); return task; }).Result;
    }

    private Object[] GetEqualTaskWithResult(string methodName)
    {
        var equalTask = tasksWithResult.FirstOrDefault(x => x.MethodName == methodName);

        if (equalTask == null)
        {
            return null;
        }
        else
        {
            return equalTask.Result;
        }
    }

    private void ForgetTasks()
    {
        Task.WaitAll(tasks.Where(x => x.Status == TaskStatus.Running || x.Status == TaskStatus.Created || x.Status == TaskStatus.WaitingToRun).ToArray());

        lock (taskForgetLocker)
        {
            if (TaskForgetEndedTasks == null)
            {
                TaskForgetEndedTasks = new Task(ForgetEndedTasks);

                TaskForgetEndedTasks.Start();
            }

            TaskForgetEndedTasks.Wait();

            TaskForgetEndedTasks = null;
        }
    }

    private void ForgetEndedTasks()
    {
        try
        {
            var completedTasks = tasks.Where(x => x.IsCompleted || x.IsFaulted || x.IsCanceled);
            var completedTasksWithResult = tasksWithResult.Where(x => x.IsCompleted || x.IsFaulted || x.IsCanceled);

            if (completedTasks.Count() > 0)
            {
                foreach (var ts in completedTasks)
                {
                    if (ts.Exception != null)
                    {
                        NLogger.Error(ts.Exception);

                        if (ts.Exception.InnerException != null)
                        {
                            NLogger.Error(ts.Exception.InnerException);
                        }
                    }

                    endedTaskIds.Add(ts.Id);
                }

                if (endedTaskIds.Count != 0)
                {
                    foreach (var t in endedTaskIds)
                    {
                        Task ct = completedTasks.FirstOrDefault(x => x.Id == t);

                        tasks.TryTake(out ct);
                    }
                }

                endedTaskIds = new ConcurrentBag<int>();
            }

            if (completedTasksWithResult.Count() > 0)
            {
                foreach (var ts in completedTasksWithResult)
                {
                    if (ts.Exception != null)
                    {
                        NLogger.Error(ts.Exception);

                        if (ts.Exception.InnerException != null)
                        {
                            NLogger.Error(ts.Exception.InnerException);
                        }
                    }

                    endedTaskWithResultIds.Add(ts.Id);
                }

                foreach (var t in endedTaskWithResultIds)
                {
                    var ct = tasksWithResult.FirstOrDefault(x => x.Id == t);

                    tasksWithResult.TryTake(out ct);
                }

                endedTaskWithResultIds = new ConcurrentBag<int>();
            }
        }
        catch(Exception ex)
        {
            NLogger.Error(ex);
        }
    }
    #endregion Conveyor

    internal void UpdateProduct(List<ProductData> products)
    {
            var updateProductDataTask = new Task(() => ADOWorker.UpdateProductData(products));

            AddTaskVoidToQueue(updateProductDataTask);
    }

    internal async Task<IEnumerable<ProductData>> GetProduct()
    {
        string methodName = "GetProductData";

        Product_Data[] result = GetEqualTaskWithResult(methodName) as Product_Data[];

        if (result == null)
        {
            var task = new MyTask<Object[]>(ADOWorker.GetProductData, methodName);

            result = await AddTaskWithResultToQueue(task) as Product_Data[];
        }

        return result;
    }
}

public class ADOWorker
{
    public Object[] GetProductData()
    {
        entities = new DataContext();

        return entities.Product_Data.ToArray();
    }

    public void UpdateProductData(List<Product_Data> products)
    {
            entities = new DataContext();

            foreach (Product_Data pr_data in products)
            {
                entities.sp_Product_Data_Upd(pr_data);
            }            
    }
}
1
Maxim

Dies kann offensichtlich sein, aber das Durchlaufen der einzelnen Tuple und das Durchführen Ihrer Arbeit in Ihrem Servlet-Container erfordert einen erheblichen Aufwand pro Datensatz.

Wenn möglich, verschieben Sie einen Teil oder die gesamte Verarbeitung auf den SQL-Server, indem Sie Ihre Logik als eine oder mehrere gespeicherte Prozeduren umschreiben.

0
Scott Smith