ошибки cuncurrency с c# потоками и задачами - PullRequest
0 голосов
/ 21 апреля 2020

Описание У меня есть пример кода для параллельного параллельного входа в систему. Требуется количество попыток входа в систему в Main и передача его в ParallelRun. ParallelRun делит longinAttemptsCount на количество потоков. Затем он порождает потоки и передает идентификатор потока и количество попыток на поток ThreadAuthenticationTaskRunner

AuthenticateAsync выполняет фактический вход в систему.

ThreadAuthenticationTaskRunner распечатывает, какой поток начиная с завершения, он выводит, какой поток закончился.

Ожидаемые результаты

Я ожидаю увидеть потоки, перечисленные в любом порядке, но я не буду видеть дублированные идентификаторы.

Фактические результаты

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

enter image description here

Что это за ошибка параллелизма, которую я вижу здесь?

using System;
using System.Threading.Tasks;
using System.Threading;

using System.Collections.Generic;

namespace Stylelabs.M.WebSdk.Examples
{
    public class Program
    {
        static void Main(string[] args)
        {
            int longinAttemptsCount = 1000;
            Console.WriteLine("Main Thread Started");

            //parallel run 
            ParallelRun(longinAttemptsCount);

            Console.WriteLine("Main Thread Ended");
        }

        /// <summary>
        /// Takes the number of required login attempts and spreads it across threads
        /// </summary>
        /// <param name="longinAttemptsCount"> Number of times to attempt to login</param>
        static void ParallelRun(int longinAttemptsCount)
        {
            int numberOfLoginAttemptsPerThread = 100;

            int numberOfThreads = longinAttemptsCount / numberOfLoginAttemptsPerThread;

            Console.WriteLine("ParallelRun Started: " + numberOfThreads + " threads");

            for (int i = 0; i < numberOfThreads; i++)
            {
                Thread thread1 = new Thread(() => ThreadAuthenticationTaskRunner(i, numberOfLoginAttemptsPerThread));
                thread1.Start();
            }

            Console.WriteLine("ParallelRun Ended: " + numberOfThreads + " threads");
        }

        /// <summary>
        /// Runs parallel logins for each thread
        /// </summary>
        /// <param name="threadId">The identifier of the running thread </param>
        /// <param name="longinAttemptsCount">Number of times to attempt to login </param>
        static async void ThreadAuthenticationTaskRunner(int threadId, int longinAttemptsCount)
        {
            Console.WriteLine("ThreadAuthenticationTaskRunner start for thread: " + threadId);

            string userName = "administrator"; //user to log in

            List<Task<String>> loginAttemptsResultsTasks = new List<Task<String>>();
            //Executing the parallel logins 
            for (int i = 0; i < longinAttemptsCount; i++)
            {
                loginAttemptsResultsTasks.Add(AuthenticateAsync(userName, i, threadId));
            }

            var loginAttemptsResults = await Task.WhenAll(loginAttemptsResultsTasks);

            foreach (string login in loginAttemptsResults)
            {
                Console.WriteLine(login);
            }
            Console.WriteLine("ThreadAuthenticationTaskRunner end for thread: " + threadId);

        }

        /// <summary>
        /// Conducts an asynchronous login on a QA tenant 
        /// </summary>
        /// <param name="userName"> The user to be logged in </param>
        /// <param name="loginId"> The login attempt identifier </param>
        /// <param name="threadId"> The identifier of the running thread </param>
        /// <returns></returns>
        static async Task<String> AuthenticateAsync(String userName, int loginId, int threadId)
        {
            String result;

            try
            {
                //some asynchronous login logic here

                result = "Success: loginId: " + loginId + " threadId: " + threadId;
            }
            catch (Exception e)
            {
                result = "Failure: loginId: " + loginId + " threadId: " + threadId + " error: " + e;
            }

            return result;
        }
    }
}

1 Ответ

0 голосов
/ 21 апреля 2020

Проблема в том, что

() => ThreadAuthenticationTaskRunner(i, numberOfLoginAttemptsPerThread)

захватывает ссылку на переменную локали i. Затем, когда функция вызывается, она фиксирует, какое значение имеет переменная в данный момент. Теперь возникает проблема, что Thread.Start может возвращаться до того, как лямбда будет вызвана до l oop, увеличивается значение для i, а затем новый thead читает неверное значение для своего идентификатора.

простое решение, о котором упоминал @stefan alreasy, состоит в том, чтобы ввести новую переменную в l oop, такую ​​как:

for (int i = 0; i < numberOfThreads; i++)
{
    var tmp = i;
    Thread thread1 = new Thread(() => ThreadAuthenticationTaskRunner(tmp, numberOfLoginAttemptsPerThread));
    thread1.Start();
}

, которая выводит лямбда-капсулу своей собственной переменной tmp, которую никто не изменит. Обратите внимание, что каждая итерация l oop будет иметь свою собственную переменную tmp и что они в основном будут жить в куче, а не в стеке.

...