首页 > 解决方案 > c# 线程和任务的并发错误

问题描述

描述 我有一个示例代码来执行线程并行在线登录。它需要尝试登录的次数Main并将其传递给ParallelRun. ParallelRun除以longinAttemptsCount线程数。然后它产生线程并传递一个线程 id 和每个线程的尝试次数ThreadAuthenticationTaskRunner

AuthenticateAsync进行实际登录。

ThreadAuthenticationTaskRunner打印出哪个线程正在启​​动,一旦完成,它就会打印出哪个线程已经结束。

预期成绩

我希望看到以任何顺序列出的线程,但我不应该看到重复的 ID。

实际结果

我看到一些线程 ID 缺失,而另一些则重复。我收到如下结果:

在此处输入图像描述

我在这里看到的这个并发错误是什么?

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;
        }
    }
}

标签: c#multithreadingtask

解决方案


这里的问题是

() => ThreadAuthenticationTaskRunner(i, numberOfLoginAttemptsPerThread)

捕获对语言环境变量的引用i。然后,当调用该函数时,它将锁定变量当前具有的值。现在出现的问题Thread.Start可能在 lambda 被调用到循环继续之前返回,增加 for 的值i,然后新的 thead 为其 id 读取错误的值。

@stefan alreasy 提到的简单解决方案是在循环中引入一个新变量,例如:

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

哇 lambda caputers 它是自己的tmp变量,没有人会改变。请注意,每个循环迭代都会有自己的tmp变量,并且它们将主要存在于堆上而不是堆栈上。


推荐阅读