dimanche 20 août 2017

C# asynchronous lottery game sometimes won't finish

I have a Lottery class in C#. It consists of an async method that may be called from an event handler (such as button click in a windows form). This async method calls a normal method that creates 4 threads. Each thread plays the lottery about 10000000 times. Right before they have finished, the threads calculate the average hit count per draw and write this back to a shared float array. The float array has as many slots as the number of threads created (i.e. 4 slots). Thread 0 locks the array object and writes the result to slot 0, Thread 1 to slot 1 and so on.

If I choose small numbers for the loop times (1000 instead of 10000000) everything works fine. But if I choose large numbers, the threads sometimes will not finish. They sometimes do not even reach the end of their methods.

I cannot figure out why.

class Lottery
{
    float[] results;
    Thread[] threads;
    Random r;

    public async Task<float[]> playAsync(int pCores)
    {
        return await Task.Run(() => play(pCores));
    }

    private float[] play(int pCores)
    {
        r = new Random();
        results = new float[pCores];
        threads = new Thread[pCores];

        for (int i = 0; i < pCores; i++)
        {
            Thread newThread = new Thread(new ParameterizedThreadStart(playLottery));
            threads[i] = newThread;
            long[] threadData = new long[] { 10000000, i };
            newThread.Start(threadData);
        }

        for (int i = 0; i < pCores; i++)
        {
            threads[i].Join();
        }

        return results;
    }

    private void playLottery(object pThreadData)
    {
        long[] threadData = (long[])pThreadData;
        long pTimes = threadData[0];
        long resultSlot = threadData[1];

        int[] numbers = new int[6];
        int[] numbersDrawn = new int[6];
        int numbersTaken = 0;

        long timeCounter = 0;

        while(numbersTaken < 6)
        {
            lock(r)
            {
                int cNumber = r.Next(1, 50);
                if (!numbers.Contains(cNumber))
                {
                    numbers[numbersTaken] = cNumber;
                    numbersTaken++;
                }
            }

        }

        float hitsPerGame = 0;
        long totalHits = 0;
        while(timeCounter < pTimes)
        {
            int hits = 0;
            int numbersDrawnTaken = 0;
            numbersDrawn = new int[numbers.Length];

            // Actual draw:
            while (numbersDrawnTaken < 6)
            {
                lock (r)
                {
                    int cNumber = r.Next(1, 50);
                    if (!numbersDrawn.Contains(cNumber))
                    {
                        numbersDrawn[numbersDrawnTaken] = cNumber;
                        numbersDrawnTaken++;
                    }
                }
            }

            //compare chosen with drawn numbers
            for(int i = 0; i < numbers.Length; i++)
            {
                for(int j = 0; j < numbers.Length; j++)
                {
                    if(numbers[i] == numbersDrawn[j])
                    {
                        hits++;
                    }
                }
            }
            totalHits += hits;
            timeCounter++;
        }

        hitsPerGame = totalHits / (float)pTimes;
        Console.WriteLine("Hitrate Thread " + resultSlot + ": " + hitsPerGame);

        lock (results)
        {
            results[resultSlot] = hitsPerGame;
        }

    }
}

numbers[] contains 6 numbers chosen by the human (simulated by random numbers in this example). numbersDrawn[] calculates 6 numbers each loop and compares those numbers to the numbers in numbers[].




Aucun commentaire:

Enregistrer un commentaire