Common usage of asynchronous multithreading in C ා

Time:2020-10-28

This article is a summary of the article. Let’s not talk nonsense. Let’s first look at some basic concepts (purely personal opinions, which may not be accurate)

Process: the sum of all running resources occupied by a program when it is running.

Thread: thread is under the management of the operating system, can also have its own computing resources, is the smallest unit of program execution flow. Any operation is done by the thread.

Multithreading: multi core CPUs work together, multiple execution streams run at the same time, which is to exchange resources for time. (single core CPU, there is no so-called multithreading).

Thread

The thread object is a thread in the non thread pool, and has its own life cycle (with the process of creation and destruction), so it cannot be reused (two threads with the same ID will not appear in one operation).

Common usage of thread:

private void button5_Click(object sender, EventArgs e) {          Console.WriteLine($"===============Method  start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is  {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}===================");
     //Starting a thread, the constructor can overload two kinds of delegates, one is no parameter and no return value, the other is with parameter and no return value
     Thread thread = new Thread(a => DoSomeThing("Thread"));
     //Current thread state
     Console.WriteLine($"thread's state is  {thread.ThreadState},thread's priority is {thread.Priority} ,thread is alived :{thread.IsAlive},thread is background:{thread.IsBackground},thread is pool threads: {thread.IsThreadPoolThread}");
     //Tells the operating system that the current thread can be executed.
     thread.Start();
     //Block the current execution thread and wait for the thread instance to execute.
     thread.Join(); 
     //The maximum waiting time is 5 seconds (whether the execution is completed or not, no longer waiting)
     thread.Join(5000); 
     Console.WriteLine($"===============Method  end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},,Thread ID is  {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}===================");
 }
 private void DoSomeThing(string name)
 {
     Console.WriteLine($"do some thing start time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is  {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}");
     long result = 0;
     for (long i = 0; i < 10000 * 10000; i++)
     {
         result += i;
     }
     Console.WriteLine($"do some thing end time is {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Thread ID is  {Thread.CurrentThread.ManagedThreadId},is back ground: {Thread.CurrentThread.IsBackground}");
 }

be careful :Thread is the foreground thread by default. It must complete the task after startup, even if the program is shut down (process exit). Thread can be specified as a background thread and will terminate as the process exits.

//False, the default is the foreground thread. The task must be completed after startup, even if the program is shut down (process exit).
Console.WriteLine(thread.IsBackground); 
thread.IsBackground  =True; // specified as background thread. (exit with process exit)

Callback usage of thread:

Thread has no callback usage like delegate in the framework. If you need a callback, you have to modify it automatically

private void CallBack(Action action, Action calback)
{
    Thread thread = new Thread(() => { action(); calback(); });
    thread.Start();
}
//No parameter, no return value
CallBack(() =>  Console.WriteLine ("OK? "), () =>  Console.WriteLine ("OK! "));
private Func CallBackReturn(Func func)
{
    T t = default(T);
    Thread thread = new Thread(() =>
    {
        t = func();
    });
    thread.Start();
    return () =>
    {
        thread.Join();
        return t;
    };
}
//Use with return value
Func func = CallBackReturn(() => DateTime.Now.Second);
Console.WriteLine ("thread not blocked");
int result = func.Invoke();
Console.WriteLine("result:" + result);

ThreadPool thread pool

The function of thread is too powerful. A little white like me can’t use it well (before I used the thread API a lot in the project, and there were many unexpected bugs). Threads in the thread pool can be reused in the same operation.

//Turn on Multithreading
 ThreadPool.QueueUserWorkItem(n => DoSomeThing("ThreadPool"));

In my opinion, try not to block the threads in the thread pool, because the number of threads in the thread pool is limited. When there are no threads available in the thread pool, deadlock will occur. If you have to wait, use it as follows:

ManualResetEvent manualResetEvent = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(n =>
{
    DoSomethingLong("ThreadPool");
    manualResetEvent.Set();
});
//Wait for the thread to complete
manualResetEvent.WaitOne();

 Task

Task is a encapsulation based on ThreadPool and belongs to the thread in the thread pool.

How to start multithreading for tasks:

Method 1: specify the starting time of the task

/// 
///To create a task using task or task, you need to specify the start time of the task (task scheduling).
/// 
public static void Demo1()
{
    Task task = new Task(() =>
    {
        Thread.Sleep(3000);         Console.WriteLine($"Current thread id is {Thread.CurrentThread.ManagedThreadId}");
    });
    task.Start (); // task scheduling (start task)
    Console.WriteLine($"Current thread name is {Thread.CurrentThread.ManagedThreadId}");
    Console.WriteLine ($"current task status:{ task.Status }");
    task.Wait (); // wait for the task to complete
    Console.WriteLine ($"current task status:{ task.Status }");
}

 

 

Method 2: create and start multithreading in one step

/// 
///Use Task.Run The () method completes the creation and start of multithreads in one step (the current thread is ready to start the task immediately).
/// 
///If you don't need to do more to create and schedule tasks, Task.Run The () method is the preferred way to create and start tasks.
/// 
/// 
public static void Demo2()
{
    Task task = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"Current thread id is {Thread.CurrentThread.ManagedThreadId}"); });
    task.Wait (); // wait until the task is completed
}

 

Mode 3: you need to pass state parameters to multithreaded tasks

/// 
///Both task and task have static property factory, which returns the default instance taskfactory
///Use Task.Factory.StartNew The () method can also be used to create and start tasks in one step.
///Currently, a state (parameter) needs to be passed to the task. You can use this method.
/// 
public static void Demo3()
{
    Task[] tasks = new Task[10];
    for (int i = 0; i < tasks.Length; i++)
    {
        tasks[i] = Task.Factory.StartNew((obj) =>
        {
            CustomData data = obj as CustomData;
            data.ThreadId = Thread.CurrentThread.ManagedThreadId;
        }, new CustomData { CreationTime = DateTime.Now.Ticks, Index = i});
    }// by blocking the current thread, wait for all sub threads to complete
    Task.WaitAll(tasks);
    foreach (var task in tasks)
    {
        //Through the asyncstate property of the task, you can get the task state (parameters provided to the task)
        var data = task.AsyncState as CustomData;
        Console.WriteLine(JsonConvert.SerializeObject(data));
    }
}
// Task.Factory.StartNew () calling a task with no return value
// Task.Factory.StartNew () calling a task with a return value

 

Task

public static void Demo4()
{
    Task[] tasks = {
Task.Factory.StartNew(() => DoComputation(1.0)),
Task.Factory.StartNew(() => DoComputation(100.0)),
Task.Factory.StartNew(() => DoComputation(1000.0)) };
    var results = new Double[tasks.Length];
    Double sum = 0;
    for (int i = 0; i < tasks.Length; i++)
    {
        // Task.Result Property contains the result of the task, and if it is called before the task is completed, the thread will be blocked until the task is completed.
        results[i] = tasks[i].Result; 
        Console.Write("{0:N1} {1}", results[i],
                          i == tasks.Length - 1 ? "= " : "+ ");
        sum += results[i];
    }
    Console.WriteLine("{0:N1}", sum);
}
private static Double DoComputation(Double start)
{
    Double sum = 0;
    for (var value = start; value <= start + 10; value += .1)
        sum += value;
    return sum;
}

 

Common API of task

Waitany and waitall will block the execution of the current thread (the main thread)

List tasks = new List();
tasks.Add(Task.Run(() => DoSomeThing("Task1")));
tasks.Add(Task.Run(() => DoSomeThing("Task2")));
tasks.Add(Task.Run(() => DoSomeThing("Task3")));
//Block the execution of the current thread, wait for any sub thread task to complete and continue to execute
Task.WaitAny(tasks.ToArray());
//Block the execution of the current thread, wait for all sub thread tasks to complete and continue to execute
Task.WaitAll(tasks.ToArray());

 

Whenall and wheany are used to achieve non blocking waiting by returning a task object

//The execution of the current thread is not blocked, and the subsequent operations are executed asynchronously after all sub thread tasks are completed
Task.WhenAll(tasks).ContinueWith(t =>
{
    Console.WriteLine ($"no blocking{ Thread.CurrentThread.ManagedThreadId }");
});

//Realization of factory mode
Task.Factory.ContinueWhenAll(tasks.ToArray(), s =>
{
Console.WriteLine (no blocking + s.length);
});

Continuewith, which is an instance mode and returns a task instance, can be executed sequentially by using this chain structure.

public static void Demo8()
{
    var task = Task.Factory
        .StartNew(() => { Console.WriteLine("1"); return 10; })
        .ContinueWith(i => { Console.WriteLine("2"); return i.Result + 1; })
        .ContinueWith(i => { Console.WriteLine("3"); return i.Result + 1; });
    Console.WriteLine(task.Result);
}

Control the use of the number of threads (the core idea comes from others, I feel that the control is good)

/// 
///Control of the number of threads
/// 
/// 
/// 
private void Test(object sender, EventArgs e)
{
    //10000 tasks completed, but only 11 threads.
    List intList = new List();
    for (int i = 0; i < 10000; i++)
    {
        intList.Add(i);
    }
    Action action = i =>
    {
        Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(new Random(i).Next(100, 300));
    };
    List tasks = new List();
    foreach (var item in intList)
    {
        int i = item;
        tasks.Add(Task.Run(() => action(i)));
        //When 11 threads have been used, the completed threads are released immediately.
        if (tasks.Count > 10)
        {
            Task.WaitAny(tasks.ToArray());
            tasks = tasks.Where(n => n.Status != TaskStatus.RanToCompletion).ToList();
        }
    }
    Task.WaitAll(tasks.ToArray());
}

Note: you should avoid using the main thread variable directly inside the child thread delegate (the disadvantage of closures)

public static void Demo5()
 {
     Task[] taskArray = new Task[10];
     for (int i = 0; i < taskArray.Length; i++)
     {
         taskArray[i] = Task.Factory.StartNew(() =>
         {
             //When you create a delegate using a lambda expression, you can access all variables that are visible within the scope of the variable.
             //But in some cases (most obviously in loops), lambda cannot capture variables as expected
             //(in this case, it can only capture the last value, not the value of each iteration). 
             //Because the timing of the task is uncertain. This problem can be avoided by passing parameters.
             Console.WriteLine (i) ; // output 10 10
         });
     }
     Task.WaitAll(taskArray);
 }
Create detached subtasks

Create a subtask in the parent task ifWhen the attachedtoparent option is not specified, the subtask does not synchronize with the parent task.

public static void Demo9()
{
    //Create parent task
    var outer = Task.Run(() =>
    {
        Console.WriteLine "The parent task starts! "";
        //Create subtask
        var child = Task.Run(() =>
        {
            Thread.SpinWait(5000000);
            Console.WriteLine ("separated task completed");
        });
    });
    outer.Wait (); // the parent task will not wait for the child task to complete
    Console.WriteLine ("parent task completed.");
}

When code running in a task creates a new task with the attachedtoparent option, the new task is called an additional subtask of the parent task. The attachedtoparent option can be used to express structured task parallelism, because the parent task implicitly waits for all additional subtasks to complete.

 public static void Demo10()
 {
     var parent = Task.Factory.StartNew(() => {
         Console.WriteLine("Parent task beginning.");
         for (int i = 0; i < 10; i++)
         {
             Task.Factory.StartNew((x) => {
                 Thread.SpinWait(5000000);
                 Console.WriteLine("Attached child #{0} completed.",x);
             }, i, TaskCreationOptions.AttachedToParent);  
         }
     });
     parent.Wait();
     Console.WriteLine("Parent task completed.");
 }

Note: if the parent task starts the denychildattach option, the subtask will not attach to the parent task even if the attachedtoparent option is enabled.

Parallel
Parallel is parallel computing, and the main thread also participates in the calculation
//Parallel.For:
public static void Main(string[] args)
 {
     //Calculate the size of the directory
     long totalSize = 0;
     String[] files =Directory.GetFiles(@"C:\Users\Administrator\Desktop");
     Parallel.For(0, files.Length,
                  index => {
                      FileInfo fi = new FileInfo(files[index]);
                      long size = fi.Length;
                      Interlocked.Add (Ref totalsize, size); // adds two 64 bit integers and replaces the first integer with and as
                  });
     Console.WriteLine("{0:N0} files, {1:N0} bytes", files.Length, totalSize); 
 }
//Rotate picture 
static void Main(string[] args)
 {
     // A simple source for demonstration purposes. Modify this path as necessary.
     string[] files = Directory.GetFiles(@"C:\Users\Administrator\Desktop\test");
     string newDir = @"C:\Users\Administrator\Desktop\test\Modified";
     if (!Directory.Exists(newDir))
         Directory.CreateDirectory(newDir);
     // Method signature: Parallel.ForEach(IEnumerable source, Action body)
     Parallel.ForEach(files, (currentFile) =>
     {
         // The more computational work you do here, the greater 
         // the speedup compared to a sequential foreach loop.
         string filename = Path.GetFileName(currentFile);
         var bitmap = new Bitmap(currentFile);
         bitmap.RotateFlip(RotateFlipType.Rotate180FlipNone);
         bitmap.Save(Path.Combine(newDir, filename));
         // Peek behind the scenes to see how work is parallelized.
         // But be aware: Thread contention for the Console slows down parallel loops!!!
         Console.WriteLine($"Processing {filename} on thread {Thread.CurrentThread.ManagedThreadId}");
         //close lambda expression and method invocation
     });
 }

 

Partition local variable

static void TestParallForeach()
 {
     int[] array = Enumerable.Range(1, 100).ToArray();
     long totalNum = 0;
     //Int is the collection element type
     //Long is the partition local variable type
     Parallel.ForEach (array, // source collection
         () = > 0, // initialize the local partition variables, and execute once for each partition
         (index, state, subtotal) = > // it is executed at each iteration
         {
             Subtotal + = index; // modify partition local variables
             Return subtotal; // the next iteration passed to the current partition
         },
         //At the end of each partition, the local partition variables of the last iteration of the partition are passed.
         (finalTotal) => Interlocked.Add(ref totalNum, finalTotal)
         );
     /*Overload mode: public static parallelloopresult foreach (IEnumerable source, 
     Func localInit, Func body, Action localFinally);
     TSource: source data type. Source: source data. The IEnumerable interface must be implemented.
     Tlocal: local partition variable type. Localinit: function to initialize local area variables. Each partition executes this function once.
     Body: this method is called every iteration of the parallel loop.
     body.TSource : the current element. body.ParallelLoopState : a variable of type parallelloopstate that can be used to retrieve the state of the loop.
     body.TLocal . 1: local partition variable.
     body.TLocal . 2: return value. Pass it to the next iteration of a particular partition loop.
     Localfinally: this delegate is called when the loop for each partition completes. * /
     Console.WriteLine(totalNum);

To be continued

 

Recommended Today

Comparison and analysis of Py = > redis and python operation redis syntax

preface R: For redis cli P: Redis for Python get ready pip install redis pool = redis.ConnectionPool(host=’39.107.86.223′, port=6379, db=1) redis = redis.Redis(connection_pool=pool) Redis. All commands I have omitted all the following commands. If there are conflicts with Python built-in functions, I will add redis Global command Dbsize (number of returned keys) R: dbsize P: print(redis.dbsize()) […]