行走即歌 阅读(100) 评论(0)

最近花了近两周时间读完了C#本质论,这本书非常喜欢,但是到后面关于多线程和同步这块,读起来就感觉有些困难了,所以做了笔记,一方面防止忘记,另一方法如果有不正确的地方,十分欢喜各位前辈不吝赐教

什么是单线程

通过一个控制台程序来认识单线程

static void Main(string[] args)
{
    var mainThread = Thread.CurrentThread;
}

在Console.WriteLine处添加一个断点,查看主线程属性

ApartmentSate:msdn的大致意思,相同对单元状态的线程之间可以相互访问对象,然而在.net中由clr以线程安全的方式管理所有共享资源

CurrentCulture和CurrentUICulture表示区域信息

ExecutionContext:封装线程相关的上下文信息

IsAlive:如果此线程已启动并且尚未正常终止或中止,则为 true;否则为 false

IsBackground:表示是否是后台线程

IsThreadPoolThread:表示是否是线程池线程

ManagedThreadId:托管线程的唯一标识符

更多可查msdn

小结:

关于线程的定义很多地方都有的,我想举一个例子,很多时候,我们人就是一个线程,早上起床,吃早饭,上班,下班......这一系列事情有序执行就是一个单线程,但是有的时候,一边听歌,一边看小说实际上就开启了第二个线程了,假如此时再写代码,那就是开启第三个线程了

使用Thread创建一个线程

const int Repetitions = 100;
static void Main(string[] args)
{
    ThreadStart threadStart = DoWork;
    Thread thread = new Thread(threadStart);
    thread.Start();
    //Main线程启动一个循环
    for (int count = 0; count < Repetitions; count++)
    {
        Console.Write('-');
    }
    Console.WriteLine("(主线程最后一个语句...)");
}
static void DoWork()
{
    for (int count = 0; count < Repetitions; count++)
    {
        Console.Write("+");
    }
}

Ctrl+F5运行,可以看到,新创建的线程和Main线程中的循环是同步执行的(多启动几次,会有不一样的发现哦!)

那么问题来了,我们创建的线程执行完了吗?程序到底什么时候结束?为什么主线程最后一句话执行完了,创建的线程还在控制台输出?

修改一下程序,Ctrl+F5,多启动几次,会有不一样的发现哦!

const int Repetitions = 100;
static int index_thread = 0;
static int index_main = 0;
static void Main(string[] args)
{
    ThreadStart threadStart = DoWork;
    Thread thread = new Thread(threadStart);
    thread.Start();
    //Main线程启动一个循环
    for (int count = 0; count < Repetitions; count++)
    {
        index_main++;
        Console.Write('-');
    }
    Console.WriteLine($"\nindex_thread:{index_thread}");
    Console.WriteLine($"index_main:{index_main}");
    Console.WriteLine("(主线程最后一个语句...)");
}
static void DoWork()
{
    for (int count = 0; count < Repetitions; count++)
    {
        index_thread++;
        Console.Write("+");
        if (count == Repetitions - 1)
        {
            Debug.Write("我创建的线程执行完成了.....................................\n");
        }
    }
}

假如你是直接按F5,可以在Visual Studio输出栏看到

结论:

1.操作系统在所有前台线程(主线程和新创建的线程都是前台线程)结束后终止进程,虽然在控制台中输出的index_thread不总是100

2.主线程以外的线程执行情况是不确定的,

3.实际上,主线程会等待所有子线程(前台线程)结束后,结束主线程,关闭进程,结束程序

4.由于子主线程执行情况的不确定性,在主线程输出index_thread的时候,可能子线程循环结束了,也可能没结束,所以导致结果总是不为100

通过Join方法阻塞主线程,等待子线程执行结束

//省略部分代码
thread.Join();
Console.WriteLine($"\nindex_thread:{index_thread}");

这样,就可以保证在此之后,子线程已经运行结束了,每次输出的结果都为100

使用线程池

const int Repetitions = 1000;
static int index_thread = 0;
static int index_main = 0;
static void Main(string[] args)
{
    WaitCallback waitCallBack = DoWork;
    ThreadPool.QueueUserWorkItem(waitCallBack, '+');
    //Main线程启动一个循环
    for (int count = 0; count < Repetitions; count++)
    {
        index_main++;
        Console.Write('-');
    }
    Console.WriteLine($"\nindex_thread:{index_thread}");
    Console.WriteLine($"index_main:{index_main}");
    Console.WriteLine("主线程最后一个语句");
}
private static void DoWork(object ch)
{
    for (int count = 0; count < Repetitions; count++)
    {
        index_thread++;
        Console.Write(ch);
        if (count == Repetitions - 1)
        {
            Debug.Write("我创建的线程执行完成了.....................................\n");
        }
    }
}

优点:

1.解决线程太多造成的性能方面的负面影响

2.高效的利用处理器

2.结合lambda使用委托,代码可以更精简

注意点

1.使用线程池创建的线程都是后台线程

2.不要使用线程池运行时间特别长的任务,尽量不要I/O受限

异步任务

static void Main(string[]  args)
{
    Task task = Task.Run(()
        =>
    {
        var t = Thread.CurrentThread;
        for (int count = 0; count < Repetitions; count++)
        {
            index_thread++;
            Console.Write('+');
        }
    });
    for (int count = 0; count < Repetitions; count++)
    {
        index_main++;
        Console.Write('-');
    }
    //类似THread.Join方法
    task.Wait();
    Console.WriteLine($"\nindex_thread:{index_thread}");
    Console.WriteLine($"index_main:{index_main}");
    Console.WriteLine("Over");
    Console.ReadLine();
}

Task是.Net Framwwork4引入的一个类库,它在使用上比Thread简单了,可控性又THreadPool强了,默认情况下,它也是从线程池中请求一个线程来执行任务.

与ThreadPool相同的是,当创建时(调用Run)启动,与Thread相同的是,可以通过Wait()方法阻塞上下文线程(主线程)等待任务执行完成

带返回值的异步任务

static void Main(string[] args)
{
    Task<string> task = Task.Run(()
        => "string 类型 返回值");
    for (int i = 0; i < 1000; i++)
    {
        if (task.IsCompleted)
        {
            Console.Write('任务完成了');
            break;
        }
        Console.Write('.');
    }
    Console.WriteLine(task.Result);
}

泛型的Task表示该任务具有返回值,IsCompleted表示任务是否完成

要注意的是,调用Result属性的时候,会阻塞上下文进程(内部执行Wait())