Description
I have a sample code to do threaded parallel online logins. It takes the number of logins to attempt in the Main
and passes it to ParallelRun
. ParallelRun
divides the longinAttemptsCount
by the number of threads. It then spawns the threads and passes a thread id and the number of attempts per thread to ThreadAuthenticationTaskRunner
AuthenticateAsync
does the actual logging in.
ThreadAuthenticationTaskRunner
prints out what thread is starting, and once done, it prints out what thread has ended.
Expected Results
I would expect to see the threads listed in any order but I should not see duplicated Ids.
Actual results
I see some thread Ids missing and others are duplicated. I receive results such as:
What is this concurrency bug I am seeing 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;
}
}
}