C# 多线程编程——理解多线程(一)

一、线程的基础知识

1.1 System.Threading.Thread类

        System.Threading.Thread是用于控制线程的基础类,通过Thread可以控制当前应用程序域中线程的创建、挂起、停止、销毁。公共属性如下:

属性名称说明
CurrentContext获取线程正在其中执行的当前上下文。
CurrentThread获取当前正在运行的线程。
ExecutionContext获取一个 ExecutionContext 对象,该对象包含有关当前线程的各种上下文的信息。
IsAlive获取一个值,该值指示当前线程的执行状态。
IsBackground获取或设置一个值,该值指示某个线程是否为后台线程。
IsThreadPoolThread获取一个值,该值指示线程是否属于托管线程池。
ManagedThreadId获取当前托管线程的唯一标识符。
Name获取或设置线程的名称。
Priority获取或设置一个值,该值指示线程的调度优先级。
ThreadState获取一个值,该值包含当前线程的状态。

1.1.1 线程优先级别

成员名称说明
Lowest可以将 Thread 安排在具有任何其他优先级的线程之后。
BelowNormal可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
Normal默认选择。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有BelowNormal 优先级的线程之前。
AboveNormal可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
Highest可以将 Thread 安排在具有任何其他优先级的线程之前。

1.1.2 线程常用方法

Thread 中包括了多个方法来控制线程的创建、挂起、停止、销毁。

方法名称说明
Abort()    终止本线程。
GetDomain()返回当前线程正在其中运行的当前域。
GetDomainId()返回当前线程正在其中运行的当前域Id。
Interrupt()中断处于 WaitSleepJoin 线程状态的线程。
Join()已重载。 阻塞调用线程,直到某个线程终止时为止。
Resume()继续运行已挂起的线程。
Start()  执行本线程。
Suspend()挂起当前线程,如果当前线程已属于挂起状态则此不起作用
Sleep()  把正在运行的线程挂起一段时间。

1.1.3 简单示例

    class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //创建一个新的线程
            Thread thread = new Thread(demoClass.Run);
            //设置为后台线程
            thread.IsBackground = true;
            //开始线程
            thread.Start();

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());//主线程
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run()
        {
            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());//子线程
        }
    }

执行结果:

二、以ThreadStart方式实现多线程

2.1 使用ThreadStart委托

        修改:在main()中通过ThreadStart委托绑定ThreadDemoClass对象的Run()方法,然后通过Thread.Start()执行异步方法。

//创建一个委托,并把要执行的方法作为参数传递给这个委托
ThreadStart threadStart = new ThreadStart(demoClass.Run);
Thread thread = new Thread(threadStart);
//或
Thread thread = new Thread(new ThreadStart(demoClass.Run));

执行结果:

请注意运行结果,在调用Thread.Start()方法后,系统以异步方式运行ThreadDemoClass.Run(),而主线程的操作是继续执行的,在ThreadDemoClass.Run()完成前,主线程已完成所有的操作。这就涉及到了线程异步或同步的问题了,这个我们后面再说。

继续上面的问题,那么如果我想要等到子线程执行完成之后再继续主线程的工作呢(当然,我觉得一般不会有这种需求)。我们可以使用 Join() 这个方法,修改之后的代码。

class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //创建一个新的线程
            Thread thread = new Thread(demoClass.Run);
            //设置为后台线程
            thread.IsBackground = true;
            //开始线程
            thread.Start();

            //等待直到线程完成
            thread.Join();

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run()
        {
            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

执行结果:

上面的代码相比之前就添加了一句 thread.Join(),它的作用就是用于阻塞后面的线程,直到当前线程完成之后。当然,还有其他的方法可以做到,比如我们现在把 thread.Join() 换成下面这句代码

//挂起 当前线程指定的时间
Thread.Sleep(100);

就当前的场景来说,这样的确可以满足需求,但是这样做有一个弊端,就是,当子线程所执行的方法逻辑比较复杂耗时较长的时候,这样的方式就不一定可以,虽然可以修改线程挂起的时间,但是这个执行的时间却是不定的。所以,Thread.Sleep() 方法一般用来设置多线程之间执行的间隔时间的。

另外,Join() 方法也接受一个参数,该参数用于指定阻塞线程的时间,如果在指定的时间内该线程没有终止,那么就返回 false,如果在指定的时间内已终止,那么就返回 true。

2.2 使用ParameterizedThreadStart委托

         当需要对线程调用的方法传入参数和接收返回值时,可以使用 ParameterizedThreadStart 委托来创建多线程,这个委托可以接受一个 object 类型的参数。

         注意:上面Thread方法和ThreadStart方法是不接受参数并且没有返回值的。

示例一:

class Program
    {static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //创建一个委托,并把要执行的方法作为参数传递给这个委托
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
            //创建一个新的线程
            Thread thread = new Thread(threadStart);
            //开始线程,并传入参数
            thread.Start("Brambling");

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run(object obj)
        {
            string name = obj as string;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + name);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

执行结果:

PS:这里我没有加这句代码了(thread.IsBackground = true,即把当前线程设置为后台线程),因为使用 thread.Start() 启动的线程默认为前台线程。那么前台线程和后台线程有什么区别呢?

前台线程就是系统会等待所有的前台线程运行结束后,应用程序域才会自动卸载。而设置为后台线程之后,应用程序域会在主线程执行完成时被卸载,而不会等待异步线程的执行完成。

示例二:

        传参是object 类型,也就是说既可以是值类型或引用类型,也可以是自定义类型。下面使用自定义类型作为参数传递。

class Program
    {
        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //创建一个委托,并把要执行的方法作为参数传递给这个委托
            ParameterizedThreadStart threadStart = new ParameterizedThreadStart(demoClass.Run);
            //创建一个新的线程
            Thread thread = new Thread(threadStart);

            UserInfo userInfo = new UserInfo();
            userInfo.Name = "Brambling";
            userInfo.Age = 333;

            //开始线程,并传入参数
            thread.Start(userInfo);

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public void Run(object obj)
        {
            UserInfo userInfo = (UserInfo)obj;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + userInfo.Name);
            Console.WriteLine("I'm " + userInfo.Age + " years old this year");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

    public class UserInfo
    {
        public string Name { get; set; }

        public int Age { get; set; }
    }

执行结果:

2.3 挂起线程

Thread.Sleep(int)阻塞主线程。

.NET专门为等待异步线程完成开发了另一个方法thread.Join(),可以保证主线程在异步线程thread运行结束后才会终止。

2.4 终止线程

        若想终止正在运行的线程,可以使用Abort()方法。在使用Abort()的时候,将引发一个特殊异常 ThreadAbortException 。若想在线程终止前恢复线程的执行,可以在捕获异常后 ,在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。
而使用Thread.Join()可以保证应用程序域等待异步线程结束后才终止运行。

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Main threadId is:" + Thread.CurrentThread.ManagedThreadId);

            Thread thread = new Thread(new ThreadStart(AsyncThread));
            thread.IsBackground = true;
            thread.Start();
            thread.Join();
        }

        //以异步方式调用
        static void AsyncThread()
        {
            try
            {
                string message = string.Format("\nAsync threadId is:{0}", Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine(message);

                for (int n = 0; n < 10; n++)
                {
                    if (n >= 4) //当n等于4时,终止线程
                    {
                        Thread.CurrentThread.Abort(n);
                    }
                    Thread.Sleep(300);
                    Console.WriteLine("The number is:" + n.ToString());
                }
            }
            catch (ThreadAbortException ex)
            {
                //输出终止线程时n的值
                if (ex.ExceptionState != null)
                    Console.WriteLine(string.Format("Thread abort when the number is: {0}!",
                                                     ex.ExceptionState.ToString()));

                Thread.ResetAbort(); //取消终止,继续执行线程
                Console.WriteLine("Thread ResetAbort!");
                Console.WriteLine("Thread Close!"); //线程结束
                Console.ReadKey();
            }
        }
    }

执行结果:

三、线程池

3.1 关于CLR线程池

        使用ThreadStart与ParameterizedThreadStart建立新线程非常简单,但通过此方法建立的线程难于管理,若建立过多的线程反而会影响系统的性能。
        出于对性能的考虑,.NET引入CLR线程池这个概念。CLR线程池并不会在CLR初始化的时候立刻建立线程,而是在应用程序要创建线程来执行任务时,线程池才初始化一个线程。线程的初始化与其他的线程一样。在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

        CLR线程池分为工作者线程(workerThreads)与I/O线程 (completionPortThreads) 两种,工作者线程是主要用作管理CLR内部对象的运作,I/O(Input/Output) 线程顾名思义是用于与外部系统交换信息。

注:通过CLR线程池所建立的线程总是默认为后台线程,优先级数为ThreadPriority.Normal。

3.2 工作者线程

        工作者线程一般有两种方式,一是直接通过 ThreadPool.QueueUserWorkItem() 方法,二是通过委托。

3.2.1 通过QueueUserWorkItem启动工作者线程

ThreadPool线程池中包含有两个静态方法可以直接启动工作者线程:
一为 ThreadPool.QueueUserWorkItem(WaitCallback)
二为 ThreadPool.QueueUserWorkItem(WaitCallback,Object) 

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();

            //设置线程池按需创建的线程的最小数量
            //第一个参数为由线程池根据需要创建的新的最小工作程序线程数
            //第二个参数为异步 I/O 线程数
            ThreadPool.SetMinThreads(5, 5);

            //设置同时处于活动状态的线程池的线程数,所有大于次数目的请求将保持排队状态,直到线程池变为可用
            //第一个参数为线程池中辅助线程的最大数目
            //第二个参数为异步 I/O 线程数
            ThreadPool.SetMaxThreads(100, 100);

            //使用委托绑定线程池要执行的方法(无参数)
            WaitCallback waitCallback1 = new WaitCallback(demoClass.Run1);
            //将方法排入队列,在线程池变为可用时执行
            ThreadPool.QueueUserWorkItem(waitCallback1);

            //使用委托绑定线程池要执行的方法(有参数)
            WaitCallback waitCallback2 = new WaitCallback(demoClass.Run1);
            //将方法排入队列,在线程池变为可用时执行
            ThreadPool.QueueUserWorkItem(waitCallback2, "Brambling");

            UserInfo userInfo = new UserInfo();
            userInfo.Name = "Brambling";
            userInfo.Age = 33;
            //使用委托绑定线程池要执行的方法(自定义类型的参数)
            WaitCallback waitCallback3 = new WaitCallback(demoClass.Run2);
            //将方法排入队列,在线程池变为可用时执行
            ThreadPool.QueueUserWorkItem(waitCallback3, userInfo);

            Console.WriteLine();
            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.ReadKey();
        }

    public class ThreadDemoClass
    {
        public void Run1(object obj)
        {
            string name = obj as string;

            Console.WriteLine();
            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + name);
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }

        public void Run2(object obj)
        {
            UserInfo userInfo = (UserInfo)obj;

            Console.WriteLine();
            Console.WriteLine("Child thread working...");
            Console.WriteLine("My name is " + userInfo.Name);
            Console.WriteLine("I'm " + userInfo.Age + " years old this year");
            Console.WriteLine("Child thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        }
    }

    public class UserInfo
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

执行结果:

        发现两个问题:第一,每次运行的时候,输出的内容的顺序都不一定是一样的(不只是线程池,前面的也是),这涉及到线程同步/异步、线程阻塞/非阻塞、线程优先级、线程安全的问题,这个在下一讲再说;第二,使用线程池建立的线程也可以选择传递参数或不传递参数,并且参数也可以是值类型或引用类型(包括自定义类型),但以上多线程示例都没有返回值,下面将会讲解。

3.2.2 异步委托

        通过ThreadPool.QueueUserWorkItem启动工作者线程虽然是方便,但WaitCallback委托指向的必须是一个带有Object参数的无返回值方法,这无疑是一种限制。若方法需要有返回值,或者带有多个参数,这将多费周折。因此,.NET提供了另一种方式去建立工作者线程,那就是委托。

3.2.2.1 利用BeginInvoke与EndInvoke完成异步委托方法

        BeginInvoke() 方法用于异步委托的执行开始;EndInvoke() 方法用于结束异步委托,并获取异步委托执行完成后的返回值;IAsyncResult.IsCompleted 用于监视异步委托的执行状态(true / false),这里的时间是不定的,也就是说一定要等到异步委托执行完成之后,这个属性才会返回 true。

    class Program
    {
        //定义一个委托类
        private delegate UserInfo MyDelegate(UserInfo userInfo);

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            List<UserInfo> userInfoList = new List<UserInfo>();
            UserInfo userInfo = null;
            UserInfo userInfoRes = null;

            //创建一个委托并绑定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);
            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //传入参数并执行异步委托
                IAsyncResult result = myDelegate.BeginInvoke(userInfo, null, null);

                //异步操作是否完成
                while (!result.IsCompleted)
                {
                    Thread.Sleep(100);

                    Console.WriteLine("Main thread working...");
                    Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
                    Console.WriteLine();
                }

                //结束异步委托,并获取返回值
                userInfoRes = myDelegate.EndInvoke(result);
                userInfoList.Add(userInfoRes);
            }

            foreach (UserInfo user in userInfoList)
            {
                Console.WriteLine("My name is " + user.Name);
                Console.WriteLine("I'm " + user.Age + " years old this year");
                Console.WriteLine("Thread ID is:" + user.ThreadId);
            }
            Console.ReadKey();
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

    public class UserInfo
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public int ThreadId { get; set; }
    }

执行结果:

3.2.2.2 利用WaitOne()方法、WaitAll() 方法和 WaitAny()完成异步委托方法

1、 WaitOne()方法,自定义一个等待的时间,如果在这个等待时间内异步委托没有执行完成,那么就会执行 while 里面的主线程的逻辑,反之就不会执行。

//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);

//阻止当前线程,直到 WaitHandle 收到信号,参数为指定等待的毫秒数
while (!result.AsyncWaitHandle.WaitOne(1000))
{
        Console.WriteLine("Main thread working...");
        Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.WriteLine();
}

2、WaitOne() 方法只能用于监视当前线程的对象,如果要监视多个对象可以使用 WaitAny(WaitHandle[], int)或 WaitAll (WaitHandle[] , int) 这两个方法。

//传入参数并执行异步委托
IAsyncResult result = myDelegate.BeginInvoke(userInfo,null,null);
IAsyncResult result1 = myDelegate.BeginInvoke(userInfo, null, null);

//定义要监视的对象,不能包含对同一对象的多个引用
WaitHandle[] waitHandles = new WaitHandle[] { result.AsyncWaitHandle, result1.AsyncWaitHandle };
while (!WaitHandle.WaitAll(waitHandles,1000))
{
        Console.WriteLine("Main thread working...");
        Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
        Console.WriteLine();
}

       WaitAll() 方法和 WaitAny() 方法都可以监视多个对象,不同的是 WaitAll() 方法需要等待所有的监视对象都收到信号之后才会返回 true,否则返回 false。而 WaitAny() 则是当有一个监视对象收到信号之后就会返回一个 int 值,这个 int 值代表的是当前收到信号的监视对象的索引。注意:在定义监视对象的时候,不能包含对同一个对象的多个引用,我这里是定义的两个示例,所以是不同的对象。

3.2.2.3 利用“回调函数”完成异步委托方法

        以上的异步委托方法可以看出,虽然使用的是异步的方式调用的方法,但是依旧需要等待异步的方法返回执行的结果,尽管我们可以不阻塞主线程,但是还是觉得不太方便,所以也就有了异步委托的回调函数。

1、回调函数无参数

    class Program
    {
        //定义一个委托类
        private delegate UserInfo MyDelegate(UserInfo userInfo);

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            UserInfo userInfo = null;

            //创建一个委托并绑定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            //创建一个回调函数的委托
            AsyncCallback asyncCallback = new AsyncCallback(Complete);

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //传入参数并执行异步委托,并设置回调函数
                IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, null);
            }

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.WriteLine();
            
            Console.ReadKey();
        }

        public static void Complete(IAsyncResult result)
        {
            UserInfo userInfoRes = null;

            AsyncResult asyncResult = (AsyncResult)result;

            //获取在其上调用异步调用的委托对象
            MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
            
            //结束在其上调用的异步委托,并获取返回值
            userInfoRes = myDelegate.EndInvoke(result);

            Console.WriteLine("My name is " + userInfoRes.Name);
            Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
            Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

执行结果:

        从上面可以看到主线程再执行了异步委托之后继续执行了下去,然后在回调函数里输出了信息,也就是说在调用了异步委托之后就不管了,把之后的结束委托和获取委托的返回值放到了回调函数中,因为回调函数是没有返回值的,但是回调函数可以有一个参数。

2、回调函数有参数

        BeginInvoke() 方法的最后两个参数,它的倒数第二个参数就是一个回调函数的委托,最后一个参数可以设置传入回调函数的参数。回调函数的参数是 object 类型,可以string 类型,也可以是自定义类型的参数,本示例以string为例。

    class Program
    {
        //定义一个委托类
        private delegate UserInfo MyDelegate(UserInfo userInfo);

        static List<UserInfo> userInfoList = new List<UserInfo>();

        static void Main(string[] args)
        {
            ThreadDemoClass demoClass = new ThreadDemoClass();
            UserInfo userInfo = null;

            //创建一个委托并绑定方法
            MyDelegate myDelegate = new MyDelegate(demoClass.Run);

            //创建一个回调函数的委托
            AsyncCallback asyncCallback = new AsyncCallback(Complete);

            //回调函数的参数
            string str = "I'm the parameter of the callback function!";

            for (int i = 0; i < 3; i++)
            {
                userInfo = new UserInfo();
                userInfo.Name = "Brambling" + i.ToString();
                userInfo.Age = 33 + i;

                //传入参数并执行异步委托,并设置回调函数
                IAsyncResult result = myDelegate.BeginInvoke(userInfo, asyncCallback, str);
            }

            Console.WriteLine("Main thread working...");
            Console.WriteLine("Main thread ID is:" + Thread.CurrentThread.ManagedThreadId.ToString());
            Console.WriteLine();
            
            Console.ReadKey();
        }

        public static void Complete(IAsyncResult result)
        {
            UserInfo userInfoRes = null;

            AsyncResult asyncResult = (AsyncResult)result;

            //获取在其上调用异步调用的委托对象
            MyDelegate myDelegate = (MyDelegate)asyncResult.AsyncDelegate;
            
            //结束在其上调用的异步委托,并获取返回值
            userInfoRes = myDelegate.EndInvoke(result);

            Console.WriteLine("My name is " + userInfoRes.Name);
            Console.WriteLine("I'm " + userInfoRes.Age + " years old this year");
            Console.WriteLine("Thread ID is:" + userInfoRes.ThreadId);

            //获取回调函数的参数
            string str = result.AsyncState as string;
            Console.WriteLine(str);
        }
    }

    public class ThreadDemoClass
    {
        public UserInfo Run(UserInfo userInfo)
        {
            userInfo.ThreadId = Thread.CurrentThread.ManagedThreadId;

            Console.WriteLine("Child thread working...");
            Console.WriteLine("Child thread ID is:" + userInfo.ThreadId);
            Console.WriteLine();

            return userInfo;
        }
    }

执行结果:

3.3 I/O线程

        I/O 线程是.NET专为访问外部资源所设置的一种线程,因为访问外部资源常常要受到外界因素的影响,为了防止让主线程受影响而长期处于阻塞状态,.NET为多个I/O操作都建立起了异步方法,例如:FileStream、TCP/IP、WebRequest、WebService等等,而且每个异步方法的使用方式都非常类似,都是以BeginXXX为开始,以EndXXX结束。

 

参考文章:

C#综合揭秘——细说多线程:http://www.cnblogs.com/leslies2/archive/2012/02/08/2320914.html#t6

C#多线程和线程池:https://www.cnblogs.com/mq0036/p/6984508.html

 

  • 11
    点赞
  • 119
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值