C# 线程、任务和同步(1)

时间:2022-09-10 18:32:52

概述

线程是程序中独立的指令流。进程包含资源,如Window句柄、文件系统句柄或其他内核对象。每个进程都分配了虚拟内存。一个进程至少包含一个线程。操作系统会调用线程。线程有一个优先级、实际上正在处理的程序的位置计数器、一个存储其局部变量的栈。每个线程都有自己的栈,但程序代码的内存和堆由一个进程的所有线程共享。这使一个进程的所有线程之间的通信非常快——该进程的所有线程都寻址相同的虚拟内存。但是,这也使处理比较困难,因为多个线程可以修改同一个内存位置。

进程管理的资源包括虚拟内存和Windows句柄,其中至少包含一个线程。线程是运行程序所必需的。

异步委托

        委托使用线程池来完成异步任务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Diagnostics;

namespace test1
{
    using cs = Console;
    class Program
    {
        //delegate int fun
        static Action<object> put = cs.WriteLine;
        static void Main(string[] args)
        {
            Func<int, int, int> fun = TakesAWhile;
            
            int nType = 3;
            if (nType == 1)
            {
                IAsyncResult returnVal = fun.BeginInvoke(1, 1000, null, null);
                //投票
                while (!returnVal.IsCompleted)
                {
                    cs.WriteLine("{0} : 1", 1);
                    Thread.Sleep(50);
                }
                put(fun.EndInvoke(returnVal));
            }
            else if (nType == 2)
            {
                IAsyncResult returnVal = fun.BeginInvoke(1, 1000, null, null);
                //句柄
                while (true)
                {
                    cs.WriteLine("{0} : 1", 1);
                    if (returnVal.AsyncWaitHandle.WaitOne(50, true))
                    {
                        break;
                    }
                }
                put(fun.EndInvoke(returnVal));
            }
            else
            {
                //异步回调
                IAsyncResult returnVal = fun.BeginInvoke(1, 1000, (IAsyncResult ar) => {
                    if (ar == null) throw new ArgumentNullException("ar");

                    Func<int, int, int> d1 = ar.AsyncState as Func<int, int, int>;
                    Trace.Assert(d1 != null, "Invalid object type");

                    put(fun.EndInvoke(ar));
                }, null);

                Thread.Sleep(2000);
            }
        }

        static int TakesAWhile(int data, int ms)
        {
            cs.WriteLine("TakesAWhile started");
            Thread.Sleep(ms);
            cs.WriteLine("TakesAWhile completed");
            return ++data;
        }

    }
}

 

1.投票

调用委托类型的Endhvokco方法。Endhvokeo方法会一直等待,直到委托完成其任务为止。

注意:如果在委托结束之前不等待委托完成其任务就结束主线程,委托线程就会停止。

2.等待句柄

这个属性返回一个WaitHandle类型的对象,它可以等待委托线程完成其任务。WaitOne()方法将一个超时时间作为可选的第一个参数,在其中可以定义要等待的最长时间。这里设置为50毫秒。如果发生超时,WaitOne()方法就返回false。

3.异步回调

使用回调方法,必须注意这个方法从委托线程中调动,而不是从主线程中调用。

Thread类

1.给线程传递数据

给线程传递一些数据可以采用两种方式。 一 种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,另 一 种方式是创建一 个自定义类 ,把线程的方法定义为实例方法,这 样就可以初始化实例的数据,之 后启动线程。

1. 使用ParameterizedThreadStart委托参数的Thread构造函数,线程的入口点必须有 一 个object类型的参数 ,且返回void类型。

using cs = Console;
class Program
{
static Action<object> put = cs.WriteLine;
static void Main(string[] args)
{
Thread thread = new Thread(print);

thread.Start(new Student { name = "Mical", id = 112 });
}

static void print(object obj)
{
Student stu = (Student)obj;
cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id);
}
}

struct Student {
public string name;
public int id;
}

 2.自定义类

    using cs = Console;
    class Program
    {
        static Action<object> put = cs.WriteLine;
        static void Main(string[] args)
        {
            print pt = new print { stu = new Student { name = "Mical", id = 112 } };
            Thread thread = new Thread(pt.put);
            thread.Start();
        }
    }

    class print
    {
        public Student stu;

        public void put()
        {
           cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id);
        }
    }


    struct Student {
        public string name;
        public int id;
    }

 

 2.后台线程

只要有一个前台线程在运行 ,应用程序的进程就在运行。如果多个前台线程在运行 ,而Main方法结束了,应用程序的进程就仍然是激活的 ,直到所有前台线程完成其任务为止。
在默认情况下,用 Thread类创建的线程是前台线程。线程池中的线程总是后台线程。在用Tread类创建线程时 ,可以设置IsBackground属性,以确定该线程是前台线程还是后台线程。

using cs = Console;
    class Program
    {
        static Action<object> put = cs.WriteLine;
        static void Main(string[] args)
        {
            print pt = new print { stu = new Student { name = "Mical", id = 112 } };
            Thread thread = new Thread(pt.put) { Name = "myThread", IsBackground = true };//设置成true,学生信息将不能正常输出,主线程结束的时候该线程就结束了;
            thread.Start();
        }
    }

    class print
    {
        public Student stu;

        public void put()
        {
            Thread.Sleep(1000);//确保主线程结束
            cs.WriteLine("name:{0}, id:{1}", stu.name, stu.id);
        }
    }


    struct Student
    {
        public string name;
        public int id;
    } 

 3.线程的优先级

线程有操作系统调度,给线程指定优先级,就可以影响调度顺序。

已经在CPU上运行的线程,如果在等待资源,它就会停止运行,并释放CPU。等待情况有多种,例如,睡眠指令、等待磁盘I/O的完成,等待网络包的到达等。如果线程不主动释放CUP,线程调度器就会抢占该线程。如果线程有个时间量,它就会继续使用CPU。如果优先级相同的多个线程等待使用CPU,线程调度器就会使用一个循环调度规则,将CPU逐个交个线程使用。如果线程被其它线程抢占,它就会排在队列的最后。

只有优先级相同的多个线程在运行,才用的上时间量和循环规则。优先级是动态的。如果线程是CPU密集型的(一直需要CPU,且不等待资源),其优先级应就低于用该线程定义的基本优先级。 

4.线程控制

调用Thread对象的Start()方法,可以创建线程。但是,在调用start()方法后,新线程仍不是处于Running状态,而是处于Unstarted状态。只要操作系统的线程调度器选择了要运行的线程,线程就会改为Running状态。读取Thread.ThreadState属性,就可以获得线程的当前状态。

 

使用Thread.Sleep()方法,会使线程处于WaitSleepJoin状态,在经历Sleep()方法定义的时间段后,线程就会等待再次被唤醒。

 

要停止另一个线程,可以调用Thread.Abort()方法。调用这个方法时,会在接到终止命令的线程中抛出一 个ThreadAbortException类型的异常。用一个处理程序捕获这个异常,线程可以在结束前完成一些清理工作。线程还可以在接收到调用Thread.ResetAbort()方法的结果ThreadAbortException异常后继续运行。 如果线程没有重置终止,接收到终止请求的线程的状态就从AbortRequested改为Aborted。


如果需要等待线程的结束,就可以调用Thresd.Join()方 法。Thread.Join()方法会停止当前线程 ,并把它设置为WaitSleepJoin状态,直到加入的线程完成为止。

 

线程池

创建线程需要时间。如果有不同的小任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。不需要自己创建这样一个列表。该列表由ThreadPool类托管。这个类会在需要时增减池中线程线程数,直到最大的线程数。池中的最大线程数是可配置的。在双核 CPU中,默认设置为1023个工作线程和1000个I/O线程。也可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。如果有更多的作业要处理,线池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。

   using cs = Console;
    class Program
    {
        static void Main()
        {
            int nWorkerThreads;
            int nCompletionPortThreads;

            ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionPortThreads);

            Console.WriteLine("Max worker threads:{0}, " +
                                "I/O completion threads:{1}",
                                nWorkerThreads, nCompletionPortThreads);

            for(int i=0; i<5; i++)
            {
                ThreadPool.QueueUserWorkItem(JobForAthread);
            }
            Thread.Sleep(3000);
        }

        static void JobForAthread(object state)
        {
            cs.WriteLine(state);

            for (int i = 0; i < 3; i++)
            {
                cs.WriteLine("loop {0}, running inside pooled thread{1}",
                    i, Thread.CurrentThread.ManagedThreadId);
                Thread.Sleep(50);
            }
        }
    }