Unity3D 成神之路C#Unity基础入门分享

C#多线程

2018-07-18  本文已影响18人  蜡笔小刘

C#多线程

一多线程的几种方式

统一用于测试的模拟下载代码

private static void DownMethod(int id,string fileName)
{
    Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName);
    for (int i = 0; i < 100; i+=10)
    {
        Thread.Sleep(10);
        Console.WriteLine("已经下载{0}", i);
    }
    Console.WriteLine("下载完毕!!!");
}
1:委托创建线程实现异步

实例1:Action委托创建线程

//多线程的第一种方式Action委托没有返回值
        public static void ThreadMethod(int id, string fileName)
        {
            DownMethod(id,fileName);
        }

        static void Main(string[] args)
        {

            //方法一:使用Action委托来异步执行方法
            Action<int, string> fileDownAction = ThreadMethod;
            //前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数
            fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar =>
            {
                Console.WriteLine("线程结束回调");
                Console.WriteLine(ar.AsyncState as string);//最后一个参数
                fileDownAction.EndInvoke(ar);//这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回
            }, "回调的参数");

            Console.WriteLine("我是主线程");
            Console.ReadKey();
        }

结果

我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程结束回调
回调的参数

实例2:Func委托创建线程

public static string FuncThreadMethod(int id, string fileName)
        {
            DownMethod(id, fileName);
            return "异步方法的返回值!!!";
        }

        static void Main(string[] args)
        {

            Func<int, string,string> fileDownAction = FuncThreadMethod;
            //前面的参数是委托所需要的参数,后面两个是固定的,一个是线程结束回调,一个是传入回调的参数
            fileDownAction.BeginInvoke(1, "天才在左疯子在右", ar =>
            {
                Console.WriteLine("线程结束回调");
                Console.WriteLine(ar.AsyncState as string);//最后一个参数
                string result = fileDownAction.EndInvoke(ar);//这个方法用于获取此线程的返回值,由于Action委托没有返回值,所以不返回
                Console.WriteLine(result);
            }, "回调的参数");

            Console.WriteLine("我是主线程");
            Console.ReadKey();
        }
    }
我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程结束回调
回调的参数
异步方法的返回值!!!
2:Thread创建异步线程

实例1:普通的方式

//使用Thread方式创建的委托方法一定没有返回值,参数可有可无,但是有参数的话一定是object类型的
public static void AsyncWithThreadMethod(object obj)
{
    Console.WriteLine("开始下载!文件名字{0}", obj as string);
    for (int i = 0; i < 100; i += 10)
    {
        Thread.Sleep(10);
        Console.WriteLine("已经下载{0}", i);
    }
    Console.WriteLine("下载完毕!!!");
}

static void Main(string[] args)
{
    //Thread创建线程的方式,Thread的构造参数一定是个无返回值的委托
    Thread thread = new Thread(AsyncWithThreadMethod);
    thread.Start("天才在左疯子在右");
    Console.WriteLine("我是主线程");
    Console.ReadKey();
}

lambda表达式方式:

Thread thread = new Thread((obj) =>
{
    Console.WriteLine("开始下载!文件名字{0}", obj as string);
    for (var i = 0; i < 100; i += 10)
    {
        Thread.Sleep(10);
        Console.WriteLine("已经下载{0}", i);
    }
    Console.WriteLine("下载完毕!!!");
});
thread.Start("天才在左疯子在右");

但是这种方式如果想要传递一些复杂的或者多个参数就不好弄了,所以可以用另一种方式,自己新建一个类来处理

class  MyThread
{
    private int id;
    private string name;

    public MyThread(int id, string name)
    {
        this.id = id;
        this.name = name;
    }

    private void DownFileStart(object callback)
    {
        Action<string> action = callback as Action<string>;
        Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, name);
        for (int i = 0; i < 100; i += 10)
        {
            Thread.Sleep(10);
            Console.WriteLine("已经下载{0}", i);
        }
        Console.WriteLine("下载完毕!!!");
        action?.Invoke("下载完毕!!!!");
    }
    
    //这个方法使用回调来处理线程执行完毕的结果
    public void Start(Action<string> callback)
    {
        Thread t = new Thread(DownFileStart);
        t.Start(callback);
    }
}

使用:

MyThread mt = new MyThread(1, "aaa");
mt.Start(o =>
{
    Console.WriteLine("线程完成回调---->{0}", o);
});
Console.WriteLine("我是主线程");
Console.ReadKey();

结果:

我是主线程
开始下载!文件ID:1,文件名字天才在左疯子在右
已经下载0
已经下载10
已经下载20
已经下载30
已经下载40
已经下载50
已经下载60
已经下载70
已经下载80
已经下载90
下载完毕!!!
线程完成回调---->下载完毕!!!!

前台线程和后台线程

前台线程:Thread创建的线程默认都是前台线程,前台线程不会跟随主线程的停止而停止,如果主线程提前停止,程序会跟着前台线程的停止而停止

后台线程:使用线程池创建的线程都是后台线程,不会被更改,主线程停止了后台线程也就随之停止了,Thread可以设置IsBackground = true;来设置为后台线程

3:线程池创建线程

线程池类ThreadPool是静态类,无法被声明和new,只能通过类名来调用里面的静态方法

一般通过线程池创建线程的方法如下:

ThreadPool.QueueUserWorkItem(state =>
    {
        Console.WriteLine("创建线程池的线程,参数:{0},线程ID:{1}", state as string, Thread.CurrentThread.ManagedThreadId);
    }, "11");

一般来说线程池用于处理多个耗时较为少的任务,不推荐处理长时间任务

4:任务(Task)

(模拟线程代码)

private static void DownMethod(int id, string fileName)
{
    Console.WriteLine("开始下载!文件ID:{0},文件名字{1}", id, fileName);
    for (int i = 0; i < 100; i += 10)
    {
        Thread.Sleep(10);
        Console.WriteLine("已经下载{0}", i);
    }
    Console.WriteLine("下载完毕!!!");
}

//多线程的第一种方式Action委托没有返回值
public static string ThreadMethod()
{
    DownMethod(1, "天才在左疯子在右");
    return "1111";
}
public static void SaveInFile(Task task)
{
    Console.WriteLine("正在存储文件");
    Thread.Sleep(1000);
    Console.WriteLine("存储完毕!!!");
}

任务有两种创建方式

  1. 泛型代表返回值
Task<string> t = new Task<string>(ThreadMethod);
t.Start();
TaskFactory<string> tf = new TaskFactory<string>();
tf.StartNew(ThreadMethod);
连续任务

当任务1依赖于任务2的时候那就需要任务2执行完毕之后执行任务1,这个时候用连续任务

 TaskFactory<string> tf = new TaskFactory<string>();
var startNew = tf.StartNew(ThreadMethod);
//主要是这句话
var startNew2 = startNew.ContinueWith(SaveInFile);
任务的层次结构

如果在一个任务中再次调用一个任务,那么内部这个任务就是外边的任务的子任务,如果子任务没有执行完毕而父任务执行完毕的话,则父任务的状态是WaitingForChildrenToComplete,只有子任务执行完了,父任务的状态就变成了RunToComplete

4:线程争用问题(同步锁)

测试类:

/// <summary>
/// 测试线程争用问题
/// </summary>
class MyThreadObject
{
    //用于测试多线程争用同一变量的变量
    private int state = 5;

    public void ChangeState()
    {
        state++;
        if (state == 5)
        {
            Console.WriteLine("state =5");//正常来讲是永远不会执行这一句的
        }
        state = 5;//当其中一个线程执行到这一句的时候另一个线程刚执行到if (state == 5),这时就会打印
    }
}

主函数:

class Program
{
    static void ChangeMyThreadState(object obj)
    {

        var myThread = obj as MyThreadObject;

        while (true)
        {
            myThread?.ChangeState();
        }
    }

    private static void Main(string[] args)
    {
    MyThreadObject myThread = new MyThreadObject();
        Thread t1 = new Thread(ChangeMyThreadState);
        t1.Start(myThread); //只有这一个线程是没有问题的
        Thread t2 = new Thread(ChangeMyThreadState);
        t2.Start(myThread); //加上这个线程就会出问题,
        Console.WriteLine("我是主线程");
        Console.ReadKey();
    }
}

结果是打印了好多。

解决方法是加同步锁

static void ChangeMyThreadState(object obj)
{
    var myThread = obj as MyThreadObject;

    while (true)
    {
        if (myThread == null) continue;
        lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
        {
            myThread.ChangeState(); //在同一时刻只有一个线程执行
        }
    }
}

注意:lock只能锁定对象(引用类型)

4:线程死锁
class Program
{
    static MyThreadObject myThread = new MyThreadObject();
    static MyThreadObject myThread1 = new MyThreadObject();

    static void ChangeMyThreadState(object obj)
    {
        while (true)
        {
            lock (myThread) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
            {
                lock (myThread1)
                {
                    myThread.ChangeState(); //在同一时刻只有一个线程执行
                    Console.WriteLine("0000");
                }
            }
        }
    }
    static void ChangeMyThreadState1(object obj)
    {
        while (true)
        {
            lock (myThread1) //像系统申请锁定需要调用的对象,如果另一个线程执行到这发现被锁定了则会等待锁定结束,然后继续执行
            {
                lock (myThread)
                {
                    myThread.ChangeState(); //在同一时刻只有一个线程执行
                    Console.WriteLine("11111");
                }
            }
        }
    }

    private static void Main(string[] args)
    {
        Thread t1 = new Thread(ChangeMyThreadState);
        t1.Start(); //只有这一个线程是没有问题的
        Thread t2 = new Thread(ChangeMyThreadState1);
        t2.Start(); //加上这个线程就会出问题,
        Console.WriteLine("我是主线程");
        Console.ReadKey();
    }
}

执行结果只打印了10几条,就停止了

上一篇下一篇

猜你喜欢

热点阅读