SoFunction
Updated on 2025-03-02

Summary of several ways to pass parameters to multiple threads in C#

Preface

Thread is defined as the execution path of the program, and each thread performs specific work. When the C# program starts, the main thread is automatically created.

Thread life cycle

  • Not started
  • Ready status
  • Not run state
  • Death state

Create a Thread without parameters

void acceptThread(){
//TODO
}
Thread threadAccept = new Thread(new ThreadStart(acceptThread));
();

Create a Thread with parameters

There are two ways to pass parameters to threads. One way is to use aParameterizedThreadStartThe Thread constructor for delegating parameters, another way isCreate a custom class, define the method of the thread as an instance method, so that the data of the instance can be initialized and then the thread can be started.

Method 1: Use ParameterizedThreadStart Delegate

If usedParameterizedThreadStartDelegate, the thread's entry must have an object-type parameter and the return type is void. Let’s look at the following example:

using System;
using ;

namespace ThreadWithParameters
{
    class Program
    {
        static void Main(string[] args)
        {
            string hello = "hello world";
            //This can also be abbreviated as Thread thread = new Thread(ThreadMainWithParameters);            //But in order to let everyone know that the ParameterizedThreadStart commission is used here, there is no abbreviation            Thread thread = new Thread(new ParameterizedThreadStart(ThreadMainWithParameters));
            (hello);
            ();
        }

        static void ThreadMainWithParameters(object obj)
        {
            string str = obj as string;
            if(!(str))
                ("Running in a thread,received: {0}", str);
        }
    }
}

What's a little troublesome here is that the parameters in the ThreadMainWithParameters method must be of object type, and we need to perform type conversion. Why must the parameters be of object type? You can find out by looking at the declaration of ParameterizedThreadStart delegation.

public delegate void ParameterizedThreadStart(object obj);   // Statement of ParameterizedThreadStart delegation

Method 2: Create a custom class

Define a class where the required fields are defined, and define the thread's main method as an instance method of the class.

using System;
using ;

namespace ThreadWithParameters
{
    public class MyThread
    {
        private string data;

        public MyThread(string data)
        {
             = data;
        }

        public void ThreadMain()
        {
            ("Running in a thread,data: {0}", data);
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            MyThread myThread = new MyThread("hello world");

            Thread thread = new Thread();
            ();

            ();
        }
    }
}

The disadvantage of this method is that when encountering a time-consuming method, create a new class.
So is there any better way to neither cast or create a new class?
Using anonymous methods

Method 3: Use anonymous method

using System;
using ;

namespace ThreadWithParameters
{
    class Program
    {
        static void Main(string[] args)
        {
            string hello = "hello world";

            //If you write Thread thread = new Thread(ThreadMainWithParameters(hello)); this form will report an error during compilation            Thread thread = new Thread(() => ThreadMainWithParameters(hello));
            ();
            ();
        }

        static void ThreadMainWithParameters(string str)
        {
             ("Running in a thread,received: {0}", str);
        }
    }
}

This way, it runs successfully without type casting or new class.

But why does this method work? After decompiling it with ildasm, it was found that the third method listed above is actually the same as the second method, except that the custom class compiler helped us do it.

The following is the IL code decompiled by the third main method:

.method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size 51 (0x33)    .maxstack  3
    .locals init ([0] class [mscorlib] thread,
             [1] class /'<>c__DisplayClass1' 'CS$<>8__locals2')
    IL_0000:  newobj     instance void /'<>c__DisplayClass1'::.ctor()
    IL_0005:  stloc.1
    IL_0006:  nop
    IL_0007:  ldloc.1
    IL_0008:  ldstr      "hello world"

   IL_000d:  stfld      string /'<>c__DisplayClass1'::hello
    IL_0012:  ldloc.1
    IL_0013:  ldftn      instance void /'<>c__DisplayClass1'::'<Main>b__0'()
    IL_0019:  newobj     instance void [mscorlib]::.ctor(object,
                                                                                     native int)
    IL_001e:  newobj     instance void [mscorlib]::.ctor(class [mscorlib])
    IL_0023:  stloc.0
    IL_0024:  ldloc.0

    IL_0025:  callvirt   instance void [mscorlib]::Start()
    IL_002a:  nop
    IL_002b:  call       int32 [mscorlib]::Read()
    IL_0030:  pop
    IL_0031:  nop
    IL_0032:  ret
  } // end of method Program::Main

Let's look at the second way of IL code:

 .method private hidebysig static void  Main(string[] args) cil managed
  {
    .entrypoint
    // Code size 44 (0x2c)    .maxstack  3
    .locals init ([0] class  myThread,
             [1] class [mscorlib] thread)
    IL_0000:  nop
    IL_0001:  ldstr      "hello world"
    IL_0006:  newobj     instance void ::.ctor(string)
    IL_000b:  stloc.0
    IL_000c:  ldloc.0

    IL_000d:  ldftn      instance void ::ThreadMain()
    IL_0013:  newobj     instance void [mscorlib]::.ctor(object,
                                                                                     native int)
    IL_0018:  newobj     instance void [mscorlib]::.ctor(class [mscorlib])
    IL_001d:  stloc.1
    IL_001e:  ldloc.1

    IL_001f:  callvirt   instance void [mscorlib]::Start()
    IL_0024:  nop
    IL_0025:  call       int32 [mscorlib]::Read()
    IL_002a:  pop
    IL_002b:  ret
  } // end of method Program::Main

Comparing the two-end code, we can find that both have a newobj. The purpose of this sentence is to initialize an instance of a class. The third method is to generate a class by the compiler: c__DisplayClass1

IL_0000:  newobj     instance void /'<>c__DisplayClass1'::.ctor()
IL_0006:  newobj     instance void ::.ctor(string)

The above is the detailed content of several methods of C# passing parameters to multi-threaded parameters. For more information about C#'s multi-threaded parameters, please pay attention to my other related articles!