C#之线程基础

创建线程

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{

    class Program
    {
        static void PrintNumbers()
        {
            Console.WriteLine("Starting to print numbers...");
            for(int i = 0; i < 10; i++) {
                {
                    Console.WriteLine(i);
                } }
        }
        public static void Main(string[] args)
        {
            var t = new Thread(PrintNumbers);
            t.Start();
            PrintNumbers();
            
        }
    }
}

output:

Starting to print numbers...
Starting to print numbers...
0
1
2
3
4
5
6
7
8
9
0
1
2
3
4
5
6
7
8
9

从结果看,PrintNumbers方法同时运行在主线程和另一个线程中。

暂停线程

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{

    class Program
    {
        static void PrintNumbers()
        {
            Console.WriteLine("Starting to print numbers...");
            for(int i = 0; i < 10; i++) {
                {
                    Console.WriteLine(i);
                } }
        }
        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting to print numbers with delay...");
            for(int i = 10; i < 20; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
        public static void Main(string[] args)
        {
            var t = new Thread(PrintNumbersWithDelay);
            t.Start();
            PrintNumbers();
            
        }
    }
}

ouput

Starting to print numbers with delay...
Starting to print numbers...
0
1
2
3
4
5
6
7
8
9
10
11


最终如下:

Starting to print numbers with delay...
Starting to print numbers...
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

程序运行时,会创建一个线程,该线程会立即执行PrintNumbersWithDelay方法中的代码,然后立即执行PrintNumber方法,而PrintNumbersWithDelay中的Sleep方法,会导致线程执行该代码时,在打印任何数字之前会等待指定的时间(2s),当线程处于休眠状态时,它会占用尽可能少的CPU时间,然后我们就发现程序先执行了PrintNumber方法。

线程等待

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{

    class Program
    {
        static void PrintNumbers()
        {
            Console.WriteLine("Starting to print numbers...");
            for(int i = 0; i < 10; i++) {
                {
                    Console.WriteLine(i);
                } }
        }
        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting to print numbers with delay...");
            for(int i = 10; i < 20; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
        public static void Main(string[] args)
        {
            var t = new Thread(PrintNumbersWithDelay);
            t.Start();
            t.Join();
            PrintNumbers();
            
        }
    }
}

output:

Starting to print numbers with delay...
10
11
12
13
14
15

最终:

Starting to print numbers with delay...
10
11
12
13
14
15
16
17
18
19
Starting to print numbers...
0
1
2
3
4
5
6
7
8
9

在主程序调用了t.Join方法,该方法允许我们等待直到t线程完成。借助这个技术,可以让一个线程等待另一个线程结束后再继续执行。

终止线程

线程的Abort方法只能用在Net Framework中,而不能 用在.Net Core中。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{

    class Program
    {
        static void PrintNumbers()
        {
            Console.WriteLine("Starting to print numbers...");
            for (int i = 0; i < 10; i++)
            {
                {
                    Console.WriteLine(i);
                }
            }
        }
        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting to print numbers with delay...");
            for (int i = 10; i < 20; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
        public static void Main(string[] args)
        {
            var t = new Thread(PrintNumbersWithDelay);
            t.Start();
            Thread.Sleep(TimeSpan.FromSeconds(6));
            t.Abort();
            Console.WriteLine("A thread has been aborted");
            PrintNumbers();

        }
    }
}

output:

Starting to print numbers with delay...
10
11
A thread has been aborted
Starting to print numbers...
0
1
2
3
4
5
6
7
8
9

值得注意的 是:调用t.Abort方法,给线程注入了ThreadAbortException方法,导致线程被终结,这非常危险,因为该异常可以在任何时刻发生并可能彻底摧毁应用程序,另外,该技术也不一定能终止线程,目标线程可以通过处理该异常,并调用Thread.ResetAbort方法来拒绝被终止。可以使用CancellationToken方法来取消线程的执行

检测线程状态

获取线程是否已经启动,或是否处于阻塞状态等相应信息是非常有用的,由于线程是独立运行的,所以其 状态在任何时候都可以被改变。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{
    class Program
    {
        static void DoNothing()
        {
            Console.WriteLine("Start to do nothing");
            Thread.Sleep(TimeSpan.FromSeconds(2));
        }
        static  void PrintNumbersWithStatus()
        {
            Console.WriteLine("Starting PrintNumbersWithStatus....");
            Console.WriteLine(Thread.CurrentThread.ThreadState.ToString());
            for(int i = 20; i < 30; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
        static void PrintNumbers()
        {
            Console.WriteLine("Starting to print numbers...");
            for (int i = 0; i < 10; i++)
            {
                {
                    Console.WriteLine(i);
                }
            }
        }
        static void PrintNumbersWithDelay()
        {
            Console.WriteLine("Starting to print numbers with delay...");
            for (int i = 10; i < 20; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(2));
                Console.WriteLine(i);
            }
        }
        public static void Main(string[] args)
        {
            Console.WriteLine("Starting program....");
            Thread t = new Thread(PrintNumbersWithStatus);
            Thread t2 = new Thread(DoNothing);
            Console.WriteLine(t.ThreadState.ToString());
            t2.Start();
            t.Start();
            for(int i = 0; i < 30; i++)
            {
                Console.WriteLine("t status:"+t.ThreadState.ToString());
                Console.WriteLine("t2 status:"+ t2.ThreadState.ToString());
            }
            Thread.Sleep(TimeSpan.FromSeconds(6));
            t.Abort();
            Console.WriteLine("A thread has been aborted");
            Console.WriteLine(t.ThreadState.ToString());
            Console.WriteLine(t2.ThreadState.ToString());

        }
    }
}

output:

Starting program....
Unstarted
Start to do nothing
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
t2 status:WaitSleepJoin
t status:Running
Starting PrintNumbersWithStatus....
Running
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
t status:WaitSleepJoin
t2 status:WaitSleepJoin
20
21
A thread has been aborted
Aborted
Stopped

值得注意的是,始终可以通过Thread.CurrentThread静态属性获取当前Thread对象。

线程优先级

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    class ThreadSample
    {
        private bool _isStopped = false;
        public void Stop()
        {
            _isStopped = true;
        }
        public void CountNumbers()
        {
            long counter = 0;
            while (!_isStopped)
                counter++;

            Console.WriteLine($"{Thread.CurrentThread.Name} with" +

                $"{Thread.CurrentThread.Priority,11} priority" +
                $"has a count ={counter,13:N0}"
                ) ;
        }
    }
    class Program
    {
        static void RunThreads()
        {
            var sample = new ThreadSample();
            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "ThreadOne";
            var threadTwo = new Thread(sample.CountNumbers);
            threadTwo.Name = "ThreadTwo";

            threadOne.Priority = ThreadPriority.Highest;
            threadTwo.Priority = ThreadPriority.Lowest;

            threadOne.Start();
            threadTwo.Start();

            Thread.Sleep(TimeSpan.FromSeconds(2));
            sample.Stop();
        }
        
        public static void Main(string[] args)
        {
            Console.WriteLine($"Current thread priority:{Thread.CurrentThread.Priority}");
            Console.WriteLine($"Running on all cores available");
            RunThreads();
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Console.WriteLine("Running on a single core");
            //该项设置,让操作系统将所有线程运行在单个CPU核心上。
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);//C#中的IntPtr类型称为“平台特定的整数类型”,它们用于本机资源,如窗口句柄。
            RunThreads();

        }
    }
}

output

Current thread priority:Normal
Running on all cores available
ThreadOne with    Highest priorityhas a count =1,146,211,615
ThreadTwo with     Lowest priorityhas a count =1,141,020,465
Running on a single core
ThreadOne with    Highest priorityhas a count =10,764,547,004
ThreadTwo with     Lowest priorityhas a count =   40,141,151

前后台线程

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    class ThreadSample
    {
        private readonly int _iterations;
        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for(int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine($"{Thread.CurrentThread.Name} prints{i}");
            }
        }
    }
    class Program
    {

        public static void Main(string[] args)
        {
            var sampleForeground = new ThreadSample(10);
            var sampleBackground = new ThreadSample(20);
            var threadOne = new Thread(sampleForeground.CountNumbers);
            var threadTwo = new Thread(sampleBackground.CountNumbers);

            threadOne.Name = "Foreground";
            threadTwo.Name = "Background";
            threadTwo.IsBackground = true;

            threadOne.Start();
            threadTwo.Start();

        }
    }
}

output:

Background prints0
Foreground prints0
Foreground prints1
Background prints1
Background prints2
Foreground prints2
Foreground prints3
Background prints3
Foreground prints4
Background prints4
Background prints5
Foreground prints5
Foreground prints6
Background prints6
Background prints7
Foreground prints7
Background prints8
Foreground prints8
Foreground prints9
Background prints9

默认情况下,显式创建的线程是前台线程,通过手动设置的IsBackground属性为true来创建一个后台线程.

前台线程与后台线程的主要区别就是:进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则不等待,直接结束工作。

        public static void Main(string[] args)
        {
            var sampleForeground = new ThreadSample(10);
            var sampleBackground = new ThreadSample(20);
            var threadOne = new Thread(sampleForeground.CountNumbers);
            var threadTwo = new Thread(sampleBackground.CountNumbers);

            threadOne.Name = "Foreground";
            threadTwo.Name = "Background";
            //threadTwo.IsBackground = true;

            threadOne.Start();
            threadTwo.Start();

        }

将设置为后台线程的代码注释掉,结果如下:

Background prints0
Foreground prints0
Foreground prints1
Background prints1
Foreground prints2
Background prints2
Foreground prints3
Background prints3
Foreground prints4
Background prints4
Background prints5
Foreground prints5
Foreground prints6
Background prints6
Foreground prints7
Background prints7
Background prints8
Foreground prints8
Foreground prints9
Background prints9
Background prints10
Background prints11
Background prints12
Background prints13
Background prints14
Background prints15
Background prints16
Background prints17
Background prints18
Background prints19

一个重要的注意事项是:如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。

向线程传递参数

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    class ThreadSample
    {
        private readonly int _iterations;
        public ThreadSample(int iterations)
        {
            _iterations = iterations;
        }
        public void CountNumbers()
        {
            for(int i = 0; i < _iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine($"{Thread.CurrentThread.Name} prints{i}");
            }
        }
    }
    class Program
    {

        static void Count(object iterations)
        {
            CountNumbers((int)iterations);
        }

        static void CountNumbers(int iterations)
        {
            for(int i = 1; i <= iterations; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(0.5));
                Console.WriteLine($"{Thread.CurrentThread.Name} Prints {i}");
            }
        }

        public static void Main(string[] args)
        {
            //方法一 :通过类的实例方法传递参数
            var sample = new ThreadSample(10);
            var threadOne = new Thread(sample.CountNumbers);
            threadOne.Name = "ThreadOne";
            threadOne.Start();
            threadOne.Join();
            Console.WriteLine("Method one".PadLeft(50, '-'));
            //方法二:通过接受一个参数的函数 
            var threadTwo = new Thread(Count);
            threadTwo.Name = "ThreadTwo";
            threadTwo.Start(8);//Start方法中赋值
            threadTwo.Join();
            Console.WriteLine("Method two".PadLeft(50, '-'));
            //方法三:通过匿名函数lambda
            var threadThree = new Thread(() => CountNumbers(12));
            threadThree.Name = "ThreadThree";
            threadThree.Start();
            threadThree.Join();
            Console.WriteLine("Mehtod three".PadLeft(50, '-'));
            //方法四
            int i = 10;
            var threadFour = new Thread(() => CountNumbers(i));//i在后面变为20,故该线程中的i也变更为20
            i = 20;
            var threadFive = new Thread(() => CountNumbers(i));
            threadFour.Name = "ThreadFour";
            threadFive.Name = "ThreadFive";
            threadFive.Start();
            threadFour.Start();
        }
    }
}

output:

ThreadOne prints0
ThreadOne prints1
ThreadOne prints2
ThreadOne prints3
ThreadOne prints4
ThreadOne prints5
ThreadOne prints6
ThreadOne prints7
ThreadOne prints8
ThreadOne prints9
----------------------------------------Method one
ThreadTwo Prints 1
ThreadTwo Prints 2
ThreadTwo Prints 3
ThreadTwo Prints 4
ThreadTwo Prints 5
ThreadTwo Prints 6
ThreadTwo Prints 7
ThreadTwo Prints 8
----------------------------------------Method two
ThreadThree Prints 1
ThreadThree Prints 2
ThreadThree Prints 3
ThreadThree Prints 4
ThreadThree Prints 5
ThreadThree Prints 6
ThreadThree Prints 7
ThreadThree Prints 8
ThreadThree Prints 9
ThreadThree Prints 10
ThreadThree Prints 11
ThreadThree Prints 12
--------------------------------------Mehtod three
ThreadFour Prints 1
ThreadFive Prints 1
ThreadFive Prints 2
ThreadFour Prints 2
ThreadFour Prints 3
ThreadFive Prints 3
ThreadFive Prints 4
ThreadFour Prints 4
ThreadFive Prints 5
ThreadFour Prints 5
ThreadFive Prints 6
ThreadFour Prints 6
ThreadFour Prints 7
ThreadFive Prints 7
ThreadFive Prints 8
ThreadFour Prints 8
ThreadFive Prints 9
ThreadFour Prints 9
ThreadFive Prints 10
ThreadFour Prints 10
ThreadFour Prints 11
ThreadFive Prints 11
ThreadFive Prints 12
ThreadFour Prints 12
ThreadFour Prints 13
ThreadFive Prints 13
ThreadFour Prints 14
ThreadFive Prints 14
ThreadFour Prints 15
ThreadFive Prints 15
ThreadFour Prints 16
ThreadFive Prints 16
ThreadFour Prints 17
ThreadFive Prints 17
ThreadFive Prints 18
ThreadFour Prints 18
ThreadFour Prints 19
ThreadFive Prints 19
ThreadFive Prints 20
ThreadFour Prints 20

C#中的lock关键字

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    abstract class CounterBase
    {
        public abstract void Increment();
        public abstract void Decrement();
    }
    class Counter : CounterBase
    {
        public int Count { get; set; }
        public override void Decrement()
        {
            Count--;
        }

        public override void Increment()
        {
            Count++;
        }
    }
    class CounterWithLock : CounterBase
    {
        private readonly object _synRoot = new object();
        public int Count { get; private set; }
        public override void Decrement()
        {
            lock (_synRoot)
            {
                Count--;
            }
        }

        public override void Increment()
        {
            lock (_synRoot)
            {
                Count++;
            }
        }
    }
    class Program
    {
        static void TestCounter(CounterBase c)
        {
            for(int i = 0; i < 100000; i++)
            {
                c.Increment();
                c.Decrement();
            }
        }
        public static void Main(string[] args)
        {
            Console.WriteLine("Incorrect counter");
            var c = new Counter();
            var t1 = new Thread(() => TestCounter(c));
            var t2 = new Thread(() => TestCounter(c));
            var t3 = new Thread(() => TestCounter(c));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();
            Console.WriteLine($"Total count:{c.Count}");
            Console.WriteLine("-".PadLeft(50, '-'));
            Console.WriteLine("Correct counter");
            var c1 = new CounterWithLock();
            t1 = new Thread(() => TestCounter(c1));
            t2 = new Thread(() => TestCounter(c1));
            t3 = new Thread(() => TestCounter(c1));
            t1.Start();
            t2.Start();
            t3.Start();
            t1.Join();
            t2.Join();
            t3.Join();
            Console.WriteLine($"Total count:{c1.Count}");
        }
    }
}

output:

Incorrect counter
Total count:33
--------------------------------------------------
Correct counter
Total count:0

当多个线程共同享用进程中同一资源时,如果出现竞争条件,则需要保证有线程操作共用资源时,其他线程必须等待直到当前线程完成操作,可以用lock实现这种行为。如果锁定了一个对象,需要访问该对象的其他所有线程均处于阻塞状态,并等待直到对该对象解除锁定。

用Monitor类锁定资源

另一个常见的多线程错误是死锁。比如:如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁,因此因为争夺资源造成的一种僵局,当进程处于这种僵持状态,若无外力作用,它们都将无法再向前推进。

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    class Program
    {
        static void LockTooMuch(object lock1,object lock2)
        {
            lock (lock1)
            {
                Thread.Sleep(1000);
                lock (lock2) { }
            }
        }
        public static void Main(string[] args)
        {
            var lock1 = new object();
            var lock2 = new object();
            new Thread(() => LockTooMuch(lock1, lock2)).Start();
            lock (lock2)
            {
                Thread.Sleep(1000);
                Console.WriteLine("Monitor.TryEnter allows not to get stuck, returning false" +
                    "after a specified timeout is elapsed"
                    );
                if (Monitor.TryEnter(lock1, TimeSpan.FromSeconds(5)))
                {
                    Console.WriteLine("Acquired a protected resource succesully");
                }
                else
                {
                    Console.WriteLine("Timeout acquiring a resource");
                }
            }

            new Thread(() => LockTooMuch(lock1, lock2)).Start();
            Console.WriteLine("-".PadLeft(50, '-'));
            lock (lock2)
            {
                Console.WriteLine("This will be a deadlock");
                Thread.Sleep(1000);
                lock (lock1)
                {
                    Console.WriteLine("Acquired a protected resource successfully");
                }
            }
        }
    }
}

output:

Monitor.TryEnter allows not to get stuck, returning falseafter a specified timeout is elapsed
Timeout acquiring a resource
--------------------------------------------------
This will be a deadlock

通过Monitor.TryEnter方法,该方法接受一个超时参数,如果在我们能够获取被lock保护的资源之前,超时参数过期,则该方法会返回false.

处理异常

在线程中try/catch是非常重要的,因为不可能在线程代码外捕获异常。

using System;
using System.Threading;
using System.Threading.Tasks;
using  System.Diagnostics;
namespace threadDemo
{
    class Program
    {
        //not recommended
        static void BadFaultyThread()
        {
            Console.WriteLine("Starting a faulty thread...");
            Thread.Sleep(TimeSpan.FromSeconds(2));
            throw new Exception("Boom!");
        }
        //recommended
        static void FaultyThread()
        {
            try
            {
                Console.WriteLine("Starting a faultyy thread...");
                Thread.Sleep(TimeSpan.FromSeconds(1));
                throw new Exception("Boom!");
            }
            catch (Exception e)
            {
                Console.WriteLine($"Exception handled:{e.Message}");
            }
        }
        public static void Main(string[] args)
        {
            var t = new Thread(FaultyThread);
            t.Start();
            t.Join();

            ///捕捉不到异常!!!只能在线程内捕捉
            try
            {
                t = new Thread(BadFaultyThread);
                t.Start();
            }
            catch(Exception e)
            {
                Console.WriteLine($"We won't get here!");
            }

            
        }
    }
}

output

Starting a faultyy thread...
Exception handled:Boom!
Starting a faulty thread...

未经处理的异常:  System.Exception: Boom!
   在 threadDemo.Program.BadFaultyThread() 位置 E:\LearningCSharp\ThreadTestNetFramework\ThreadDemo\Program.cs:行号 14
   在 System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   在 System.Threading.ThreadHelper.ThreadStart()
请按任意键继续. . .

发送信号:

using System.Diagnostics;

var signal = new ManualResetEvent(false);
var t = new Thread(() =>
{
    Console.WriteLine("watiging for signal....");
    Console.WriteLine("");
    Console.Write("输入");
    Console.ForegroundColor = ConsoleColor.Green;
    Console.Write("n");
    Console.ResetColor();
    Console.Write("发送信号:");
    signal.WaitOne();
    signal.Dispose();
    Console.WriteLine("Got signal");
});
t.Start();


var a = Console.ReadLine();
if (a == "n")
{
    signal.Set();
}

posted @ 2022-02-07 22:38  JohnYang819  阅读(43)  评论(0编辑  收藏  举报