C#多线程

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几条,就停止了

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 此篇文章简单总结了C#中主要的多线程实现方法,包括Thread、ThreadPool、Parallel和Task类...
    _xinchen阅读 19,675评论 4 4
  • 1.软件片段的架构是一套控制软件操作的规则、模式、进程、执行协议和断言。 多线程架构:一种将工作模式分解为两个或更...
    程序爱好者阅读 4,307评论 0 0
  • 异步委托 投票,并检查委托是否完成了任务所创建的Delegate类提供了BeginInvoke()方法,该方法中,...
    北冥冰皇阅读 372评论 0 2
  • 原文地址:Threading in C# part 1 : 概念 当线程开始执行(thread.Start()),...
    fat___lin阅读 272评论 0 0
  • 姓名夏钢,无锡夏利达公司 【日精进打卡第141天】 【知~学习】 《六项精进》2遍共2遍 《大学》1遍共1遍 【经...
    Atun0219阅读 100评论 0 0