Amit Ranjan
Amit Ranjan

Reputation: 214

Singleton object creation on multiple different threads using TPL

This is just a self learning situation. I am pretty new to TPL and Threading. Anyways, I am using a generic Singleton class and creating ~10K instances to check if my code is returning same instance or creating new instance everytime. I am creating instances asynchronously using Task Factory inside a for loop. To validate the creation of instance, i am returning a string having these info as a list of string:

  1. Iteration Counter
  2. Name of instance
  3. Hashcode of instance and
  4. ThreadId and displaying the list of strings to listbox.

My Queries

On running, I found few things,

  1. the value of i inside the for loop is getting duplicated for the different intances
  2. for those 10K iterations, i have only 8-9 threads created, instead of expected 10k threads. I was expectig 10K threads to popup , do their individual task and then disappear gracefully.
  3. Can I use this in my projects, as class libraries, irrespective of the platforms - Web, Windows or Mobile?

Please do leave a note on my 1OK threads thoughts :). Whether its a good idea / bad idea on multithreading?

My code

Singleton Class

public sealed class Singleton<T> where T : class
{
    static Singleton() {  }
    private Singleton() { }
    public static T Instance { get; } = Activator.CreateInstance<T>();
}

Class: SingletonInThread

public class SingletonInThread
{

    /// <summary>
    /// Method responsible for creation of same instance, 10K times using Task.Factory.StartNew()
    /// </summary>
    /// <returns></returns>
    public async Task<IEnumerable<string>> LoopAsync()
    {
        List<Task<string>> list = new List<Task<string>>();
        for (int i = 0; i <= 9999; i++)
        {
            list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(i)));
        }
       return  await Task.WhenAll<string>(list);
    }

    /// <summary>
    /// Creates new instance of Logger and logs its creation with few details. Kind of Unit of Work.
    /// </summary>
    /// <param name="i"></param>
    /// <returns></returns>
    private string CreateAndLogInstances(int i)
    {
        var instance = Singleton<Logger>.Instance;
        return $"Instance{i}. Name of instance= {instance.ToString()} && Hashcode ={instance.GetHashCode()} && ThreadId= {Thread.CurrentThread.ManagedThreadId}";
    }

}

Frontend _ On UI side, On buttonclick event, populating listbox

private async void button1_Click(object sender, EventArgs e)
{
    IEnumerable<string> list = await new SingletonInThread().LoopAsync();

    foreach (var item in list)
    {
        listBox1.Items.Add(item);

    }
}

Also, I noticed one thing that my UI gets blocked while populating list box with 10K items. Please do help me populating it asynchronous way. I knew the bgworker, begininvoke and methodinvoker. Is there anything other than the too in TPL??

Output

enter image description here

---

Update

As suggested if I use Parallel.For,then, instead of 10K strings, I am getting a random figure of 9491, 9326 etc. I.e. less than 10K. I dont know why????

Here's my updated code for LoopAsync method using Parallel.For

public  IEnumerable<string> LoopAsync()
{
     List<string> list = new List<string>();
           
     Parallel.For(0, 9999, i =>
     {
          list.Add( CreateAndLogInstances(i));
     });
     return list;
}

Upvotes: 1

Views: 467

Answers (2)

Stephen Cleary
Stephen Cleary

Reputation: 456647

the value of i inside the for loop is getting duplicated for the different intances

This doesn't have anything to do with threading/parallel/asynchrony or singleton instances. You're seeing this because closures capture variables, not values. So this code:

for (int i = 0; i <= 9999; i++)
{
  list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(i)));
}

is passing the variable i to the closure () => CreateAndLogInstances(i), not the current value of i. To capture the current value and use that in your closure, you would need a separate variable per closure, as recommended in a comment:

for (int i = 0; i <= 9999; i++)
{
  var index = i;
  list.Add(Task.Factory.StartNew(()=>  CreateAndLogInstances(index)));
}

for those 10K iterations, i have only 8-9 threads created, instead of expected 10k threads. I was expectig 10K threads to popup , do their individual task and then disappear gracefully.

No, you would very much not want that to happen. Thread creation and destruction has a lot of overhead. StartNew and Parallel queue work to the thread pool, and the thread pool will grow quickly to a certain point and then grow slowly, on purpose. This is because on, e.g., an 8-core machine, there is no point in having 10k threads because they cannot all run anyway.

Can I use this in my projects, as class libraries, irrespective of the platforms - Web, Windows or Mobile?

I never recommend using parallel processing on web applications, because your web host has already parallelized your requests. So doing additional parallel processing tends to burden your web server and potentially make it much less responsive to other requests.

Also, I noticed one thing that my UI gets blocked while populating list box with 10K items. Please do help me populating it asynchronous way.

You normally want to avoid making 10k UI updates at practically the same time. Parallel processing doesn't help with a UI because all UI updates have to be done on the UI thread. Either put all the results in the list with a single call, or use something like control virtualization.

Upvotes: 1

Eric J.
Eric J.

Reputation: 150118

Adding the same object to a WinForms list box multiple times results in multiple lines in the list box, e.g.:

    private void Form1_Load(object sender, EventArgs e)
    {
        string foo = "Hello, world";
        listBox1.Items.Add(foo);
        listBox1.Items.Add(foo);
        listBox1.Items.Add(foo);
    }

yields three lines proclaiming Hello, world. So, it isn't unexpected that you receive 10,000 lines in your example. But are they the same object, or are you creating multiple objects?

I created my own Logger class:

public class Logger
{
    static private Random rnd = new Random();
    public int Id { get; } = rnd.Next();
    public override string ToString()
    {
        return Id.ToString();
    }
}

Indeed, each output line has the same Id, thus indicating the same object instance was used in each case. You also output the call to GetHashCode(), which also is the same in each case, indicating a high probability that you are dealing with only one instance.

Upvotes: 0

Related Questions