【问题标题】:cuncurrency bugs with c# threads and tasksc# 线程和任务的并发错误
【发布时间】:2020-04-21 10:06:08
【问题描述】:

说明 我有一个示例代码来执行线程并行在线登录。在Main 中尝试登录的次数并将其传递给ParallelRunParallelRunlonginAttemptsCount 除以线程数。然后它产生线程并将线程 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;
        }
    }
}

【问题讨论】:

  • 似乎您遇到了int i 不是通过引用而不是通过值传递的问题。尝试将值复制到循环内的 tmp 变量:for (int i = 0; ....) { var copy = i; ..Runner(copy, ...)
  • @stefan hmmm :) 确实如此。这是 C# 问题还是一般并发问题?谢谢!
  • 它是您正在创建的闭包的详细信息 () => ThreadAuthenticationTaskRunner(i, numberOfLoginAttemptsPerThread) - 不确定它是特定于 c# 还是只是闭包标准实现的一般事物。不会认为这是一个问题,只是您需要了解的实现细节
  • 附带说明,比起Thread,更喜欢Task.Run。将async voidThread 一起使用可能会导致一些令人惊讶的行为。

标签: c# multithreading task


【解决方案1】:

这里的问题是

() => ThreadAuthenticationTaskRunner(i, numberOfLoginAttemptsPerThread)

捕获对语言环境变量i 的引用。然后,当调用该函数时,它将锁定变量当前具有的值。现在的问题是 Thread.Start 可能会在 lambda 被调用之前返回,循环继续,增加 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 变量,并且它们大部分将存在于堆上而不是堆栈上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-14
    • 2015-11-10
    • 1970-01-01
    • 2010-12-18
    • 1970-01-01
    相关资源
    最近更新 更多