Обход небинарной древовидной структуры из TwinCat с несколькими потоками в С#

Я пытаюсь оптимизировать алгоритм поиска, который я использую для поиска отмеченных символов в TwinCat 3 через интерфейс ADS. Вопрос не связан с TwinCat, так что пока не пугайтесь.

Проблемы: Символы не загружаются сразу. Я думаю, что библиотека TwinCatAds использует ленивую загрузку. Символы имеют древовидную структуру небинарного несбалансированного дерева.

Решение: вы можете открыть более одного потока для ADS. И обрабатывать потоки в нескольких потоках.

Вопрос в том, что первый уровень символов я делю на количество ядер процессора. Итак, поскольку дерево несбалансировано, некоторые потоки заканчиваются быстрее, чем другие. Из-за этого мне нужно более приятное решение, как разделить работу между потоками.

PS: я не могу использовать Parallel.ForEach(). Из-за потоков это приводит к тому же или большему количеству времени, что и решение с одним потоком.

Мой тестовый код выглядит так, он просто считает все символы огромного проекта.

using TwinCAT.Ads;
using System.Threading;
using System.IO;
using System.Diagnostics;
using System.Collections;


namespace MultipleStreamsTest
{
class Program
{
    static int numberOfThreads = Environment.ProcessorCount;
    static TcAdsClient client;
    static TcAdsSymbolInfoLoader symbolLoader;
    static TcAdsSymbolInfoCollection[] collection = new TcAdsSymbolInfoCollection[numberOfThreads];
    static int[] portionResult = new int[numberOfThreads];
    static int[] portionStart = new int[numberOfThreads];
    static int[] portionStop = new int[numberOfThreads];

    static void Connect()
    {
        client = new TcAdsClient();
        client.Connect(851);
        Console.WriteLine("Conected ");
    }
    static void Main(string[] args)
    {
        Connect();
        symbolLoader = client.CreateSymbolInfoLoader();
        CountAllOneThread();
        CountWithMultipleThreads();
        Console.ReadKey();
    }        
    static public void CountAllOneThread()
    {
        Stopwatch stopwatch = new Stopwatch();
        int index = 0;
        stopwatch.Start();
        Console.WriteLine("Counting with one thread...");
        //Count all symbols
        foreach (TcAdsSymbolInfo symbol in symbolLoader)
        {                
            index++;
        }
        stopwatch.Stop();
        //Output
        Console.WriteLine("Counted with one thred " + index + " symbols in " + stopwatch.Elapsed);
    }
    static public int countRecursive(TcAdsSymbolInfo symbol)
    {
        int i = 0;
        TcAdsSymbolInfo subSymbol = symbol.FirstSubSymbol;
        while (subSymbol != null)
        {
            i = i + countRecursive(subSymbol);
            subSymbol = subSymbol.NextSymbol;
            i++;
        }
        return i;
    }
    static public void countRecursiveMultiThread(object portionNum)
    {
        int portionNumAsInt = (int)portionNum;
        for (int i = portionStart[portionNumAsInt]; i <= portionStop[portionNumAsInt]; i++)
        {
                portionResult[portionNumAsInt] += countRecursive(collection[portionNumAsInt][i]);//Collection Teil 
        }
    }
    static public void CountWithMultipleThreads()
    {
        Stopwatch stopwatch = new Stopwatch();
        int sum = 0;
        stopwatch.Start();
        Console.WriteLine("Counting with multiple thread...");
        for (int i = 0; i < numberOfThreads; i++)
        {
            collection[i] = symbolLoader.GetSymbols(true);
        }
        int size = (int)(collection[0].Count / numberOfThreads);
        int rest = collection[0].Count % numberOfThreads;
        int m = 0;
        for (; m < numberOfThreads; m++)
        {
            portionStart[m] = m * size;
            portionStop[m] = portionStart[m] + size - 1;
        }
        portionStop[m - 1] += rest;

        Thread[] threads = new Thread[numberOfThreads];
        for (int i = 0; i < numberOfThreads; i++)
        {
            threads[i] = new Thread(countRecursiveMultiThread);
            threads[i].Start(i);
            Console.WriteLine("Thread #" + threads[i].ManagedThreadId + " started, fieldIndex: " + i);
        }
        //Check when threads finishing:
        int threadsFinished = 0;
        bool[] threadFinished = new bool[numberOfThreads];
        int x = 0;
        while (true)
        {
            if (threads[x].Join(10) && !threadFinished[x] )
            {
                Console.WriteLine("Thread #" + threads[x].ManagedThreadId + " finished ~ at: " + stopwatch.Elapsed);
                threadsFinished++;
                threadFinished[x] = true;                    
            }
            x++;
            x = x % numberOfThreads;
            if (threadsFinished == numberOfThreads) break;
            Thread.Sleep(50);
        }            
        foreach (int n in portionResult)
        {
            sum += n;
        }
        sum += collection[0].Count;
        stopwatch.Stop();
        //Output
        Console.WriteLine("Counted with multiple threds in Collection " + sum + " symbols " + " in " + stopwatch.Elapsed);
        for (int i = 0; i < numberOfThreads; i++)
        {
            Console.WriteLine("#" + i + ": " + portionResult[i]);
        }
    }
}
}

Вывод консоли:

Если вы пытаетесь запустить код, используйте версию TwinCat.Ads 4.0.17.0 (которую я использую). Они что-то сломали в новой версии, доступной с NuGet.


person Jo Re    schedule 27.10.2016    source источник


Ответы (1)


Создайте пул потоков и отслеживайте запущенные и неактивные потоки. В каждой ветке проверяйте, есть ли простаивающие потоки, есть ли назначенный поток для подветви.

person J.R.    schedule 27.10.2016