0

The code below is simplified. There is a massive method which iterates many times. The threaded method receives an element of a List<Class>.

Because the threaded method modifies an element which is an object in the method, I do not want that the same argument is loaded on the separate threads concurrently. Because each element is independent, I want that the method with each one runs concurrently.

How to run the method with the same argument sequentially and run the method with a different argument concurrently?

Do I have to verify each running thread one by one before New & Start the method, whether there is the method with the same argument or not?

class Class1
{
    // something
}

private void Form1_Load(object sender, EventArgs e)
{
    List<Class1> _List = new();
            
    for (int i = 0; i < 10; i++)
    {
        _List.Add(new Class1 { });
    }
            
    for (int i = 0; i < 10; i++)
    {
        Thread _Thread = new(Method1);

        _Thread.Start(_List[new Random().Next(10)]); // the argument can be consecutively same element of List1
    }
}

void Method1(object _Object)
{
    // modifies _Object
}
Austin
  • 2,203
  • 3
  • 12
  • 28
SHIN JaeGuk
  • 494
  • 1
  • 5
  • 14
  • 1
    I'd recommend to replace the second loop with `Parallel.ForEach(_List, Method1);`, it will achieve the same, but simpler and more efficiently. – Pavel Tupitsyn Sep 20 '22 at 17:28
  • 2
    Material for study: [Threading in C# - Basic synchronization](https://www.albahari.com/threading/part2.aspx) by ​Joseph Albahari. – Theodor Zoulias Sep 20 '22 at 17:53
  • @PavelTupitsyn I thought that. But I am not sure that the initializing cost of Parallel.ForEach is cheaper than that of ThreadPool. In the above code I did not use ThreadPool but in actual code I would. May I appreciate in advance your further comment? – SHIN JaeGuk Sep 20 '22 at 18:04
  • 2
    @SHINJaeGuk `Parallel.ForEach` uses `ThreadPool` underneath. The only way to tell which is faster is to measure your actual scenario. – Pavel Tupitsyn Sep 20 '22 at 18:07

3 Answers3

2
  1. If a random order is really required, randomize the list before using it: Randomize a List<T>

  2. Creating new threads is expensive, prefer ThreadPool

  3. To process a list of items in parallel, use Parallel.ForEach(_List, Method1)

Pavel Tupitsyn
  • 8,393
  • 3
  • 22
  • 44
0

You should use Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:

void Main()
{
    var foos = new [] { new Foo("A"), new Foo("B"), new Foo("C"), new Foo("D") };
    
    var query =
        from n in Observable.Range(0, 1000).Select(n => __random.Next(foos.Length))
        group foos[n] by foos[n].Name into grouped_foos
        from gf in grouped_foos.ObserveOn(Scheduler.Default)
        select new { Foo = gf, ManagedThreadId = Method1(gf) };

    query
        .ToArray()
        .Select(x => x.GroupBy(y => (y.Foo, y.ManagedThreadId)).Select(y => (y.Key.Foo, y.Key.ManagedThreadId, Count: y.Count())))
        .Subscribe(xs =>
            Console.WriteLine(
                String.Join(
                    Environment.NewLine,
                    xs.Select(x => $"{x.Count} x {x.Foo.Name} {x.Foo.Counter} run on thread {x.ManagedThreadId}"))));
}

int Method1(Foo foo)
{
    foo.Counter = foo.Counter + 1;
    return System.Threading.Thread.CurrentThread.ManagedThreadId;
}


private static Random __random = new Random();

public class Foo
{
    public int Counter { get; set; } = 0;
    public string Name { init; get; }
    public Foo(string name)
    {
        this.Name = name;
    }
    public override int GetHashCode() => this.Name.GetHashCode();
    public override bool Equals(object obj) => obj is Foo f ? this.Name.Equals(f.Name) : false;
}

When I run that I get this typical output:

259 x A 259 run on thread 38
239 x D 239 run on thread 14
238 x C 238 run on thread 11
264 x B 264 run on thread 36

The code is creating a sequence of 1_000 indices randomly selecting a Foo from 4 distinct instances. It then groups by those instances and performs some processing. The processing returns the managed thread id to see which thread ran which code.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
-1

The whole range lock (object) of Method2 implements only same objects in order consecutively.

List<int> List1 = new();
List<int> List2 = new();

void Method1(object _Object)
{
    List<int> _List = (List<int>)_Object;

    for (int i = 0; i < 30; i++)
    {
        _List.Add(i);

        Thread.Sleep(100);
    }
}

void Method2(object _Object)
{
    List<int> _List = (List<int>)_Object;

    lock (_List)
    {
        for (int i = 0; i < 30; i++)
        {
            _List.Add(i);

            Thread.Sleep(100);
        }
    }
}

private void button2_Click(object sender, EventArgs e)
{
    List1.Clear();
    List2.Clear();

    ThreadPool.QueueUserWorkItem(Method1, List1);
    ThreadPool.QueueUserWorkItem(Method1, List2);
}

private void button3_Click(object sender, EventArgs e)
{
    List1.Clear();

    ThreadPool.QueueUserWorkItem(Method2, List1);
    ThreadPool.QueueUserWorkItem(Method2, List1);
}
SHIN JaeGuk
  • 494
  • 1
  • 5
  • 14