Threads(异步和多线程)

原文:Threads(异步和多线程)

Task是.NET Framework3.0出现的,线程是基于线程池的,然后提供丰富的api,Thread方法很多很强大,但是太过强大,没有限制。

DoSomethingLong方法如下:

 /// <summary>
 /// 一个比较耗时耗资源的私有方法
 /// </summary>
 /// <param name="name"></param>
 private void DoSomethingLong(string name)
 {
     Console.WriteLine($"****************DoSomethingLong Start  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")}***************");
     long lResult = 0;
     for (int i = 0; i < 1_000_000_000; i++)
     {
         lResult += i;
     }
     Thread.Sleep(2000);

     Console.WriteLine($"****************DoSomethingLong   End  {name}  {Thread.CurrentThread.ManagedThreadId.ToString("00")} {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")} {lResult}***************");
 }

Task的使用:

{
    Task task = new Task(() => this.DoSomethingLong("btnTask_Click_1"));
    task.Start();
}
{
    Task task = Task.Run(() => this.DoSomethingLong("btnTask_Click_2"));
}
{
    TaskFactory taskFactory = Task.Factory;
    Task task = taskFactory.StartNew(() => this.DoSomethingLong("btnTask_Click_3"));
}

如果这样去调用:

ThreadPool.SetMaxThreads(8, 8);
for (int i = 0; i < 100; i++)
{
    int k = i;
    Task.Run(() =>
    {
        Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(2000);
    });
}

如果去掉设置最大线程的代码:

for (int i = 0; i < 100; i++)
{
    int k = i;
    Task.Run(() =>
    {
        Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        Thread.Sleep(2000);
    });
}

运行结果如下:

ThreadPool.SetMaxThreads(8, 8);

  线程池是单例的,全局唯一的,设置后,同时并发的Task只有8个,而且是复用的,Task的线程是源于线程池的,全局的,请不要这样设置。

假如我想控制下Task的并发数量,改怎么做?

{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Console.WriteLine("在Sleep之前");
    Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续
    Console.WriteLine("在Sleep之后");
    stopwatch.Stop();
    Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
}
{
    Stopwatch stopwatch = new Stopwatch();
    stopwatch.Start();
    Console.WriteLine("在Delay之前");
    Task task = Task.Delay(2000)
        .ContinueWith(t =>
        {
            stopwatch.Stop();
            Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");

            Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        });//异步等待--等待2s后启动新任务
    Console.WriteLine("在Delay之后");
    stopwatch.Stop();
    Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
}

运行结果如下:

  

如果将最后一个stopwatch注释掉:

 {
     Stopwatch stopwatch = new Stopwatch();
     stopwatch.Start();
     Console.WriteLine("在Sleep之前");
     Thread.Sleep(2000);//同步等待--当前线程等待2s 然后继续
     Console.WriteLine("在Sleep之后");
     stopwatch.Stop();
     Console.WriteLine($"Sleep耗时{stopwatch.ElapsedMilliseconds}");
 }
 {
     Stopwatch stopwatch = new Stopwatch();
     stopwatch.Start();
     Console.WriteLine("在Delay之前");
     Task task = Task.Delay(2000)
         .ContinueWith(t =>
         {
             stopwatch.Stop();
             Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");

             Console.WriteLine($"This is ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         });//异步等待--等待2s后启动新任务
     Console.WriteLine("在Delay之后");
     //stopwatch.Stop();
     //Console.WriteLine($"Delay耗时{stopwatch.ElapsedMilliseconds}");
 }

什么时候用多线程?

  任务并发是时候

多线程能干嘛?

  提升速度,优化用户体验。

比如,现在有一个场景,在公司开会,领导在分配任务,不能并发,因为只能有一个领导在讲话分配任务,当任务分配下去,开发们确实可以同时开始撸代码,这个是可以并发的。

 TaskFactory taskFactory = new TaskFactory();
 List<Task> taskList = new List<Task>();
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", "  DBA ")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat")));

现在要求,谁第一个完成,获得红包奖励(ContinueWhenAny);所有完成后,一起庆祝下(ContinueWhenAll),将其放入一个List<Task>里面去

 TaskFactory taskFactory = new TaskFactory();
 List<Task> taskList = new List<Task>();
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle1", "Portal")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle2", "  DBA ")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle3", "Client")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle4", "BackService")));
 taskList.Add(taskFactory.StartNew(() => this.Coding("bingle5", "Wechat")));

 //谁第一个完成,获取一个红包奖励
 taskFactory.ContinueWhenAny(taskList.ToArray(), t => Console.WriteLine($"XXX开发完成,获取个红包奖励{Thread.CurrentThread.ManagedThreadId.ToString("00")}"));
 //项目完成后,一起庆祝一下
 taskList.Add(taskFactory.ContinueWhenAll(taskList.ToArray(), rArray => Console.WriteLine($"开发都完成,一起庆祝一下{Thread.CurrentThread.ManagedThreadId.ToString("00")}")));

ContinueWhenAny  ContinueWhenAll 非阻塞式的回调;而且使用的线程可能是新线程,也可能是刚完成任务的线程,唯一不可能是主线程

//阻塞当前线程,等着任意一个任务完成
Task.WaitAny(taskList.ToArray());//也可以限时等待
Console.WriteLine("准备环境开始部署");
//需要能够等待全部线程完成任务再继续  阻塞当前线程,等着全部任务完成
Task.WaitAll(taskList.ToArray());
Console.WriteLine("5个模块全部完成后,集中点评");

  Task.WaitAny  WaitAll都是阻塞当前线程,等任务完成后执行操作,阻塞卡界面,是为了并发以及顺序控制,网站首页:A数据库 B接口 C分布式服务 D搜索引擎,适合多线程并发,都完成后才能返回给用户,需要等待WaitAll,列表页:核心数据可能来自数据库/接口服务/分布式搜索引擎/缓存,多线程并发请求,哪个先完成就用哪个结果,其他的就不管了。

 假如说我想控制下Task的并发数量,该怎么做?  20个

 List<Task> taskList = new List<Task>();
 for (int i = 0; i < 10000; i++)
 {
     int k = i;
     if (taskList.Count(t => t.Status != TaskStatus.RanToCompletion) >= 20)
     {
         Task.WaitAny(taskList.ToArray());
         taskList = taskList.Where(t => t.Status != TaskStatus.RanToCompletion).ToList();
     }
     taskList.Add(Task.Run(() =>
     {
         Console.WriteLine($"This is {k} running ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
         Thread.Sleep(2000);
     }));
 }

Parallel并发执行多个Action线程,主线程会参与计算---阻塞界面。等于TaskWaitAll+主线程计算

 Parallel.Invoke(() => this.DoSomethingLong("btnParallel_Click_1"),
     () => this.DoSomethingLong("btnParallel_Click_2"),
     () => this.DoSomethingLong("btnParallel_Click_3"),
     () => this.DoSomethingLong("btnParallel_Click_4"),
     () => this.DoSomethingLong("btnParallel_Click_5"));
Parallel.For(0, 5, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
Parallel.ForEach(new int[] { 0, 1, 2, 3, 4 }, i => this.DoSomethingLong($"btnParallel_Click_{i}"));

ParallelOptions options = new ParallelOptions();
options.MaxDegreeOfParallelism = 3;
Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));

有没有办法不阻塞?

Task.Run(() =>
{
    ParallelOptions options = new ParallelOptions();
    options.MaxDegreeOfParallelism = 3;
    Parallel.For(0, 10, options, i => this.DoSomethingLong($"btnParallel_Click_{i}"));
});

  几乎90%以上的多线程场景,以及顺序控制,以上的Task的方法就可以完成,如果你的多线程场景太复杂搞不定,那么请梳理一下你的流程,简化一下。建议最好不要线程嵌套线程,两三次勉强能懂,三层就hold不住了,更多的只能求神。

多线程异常:

try
{

    List<Task> taskList = new List<Task>();
    for (int i = 0; i < 100; i++)
    {
        string name = $"btnThreadCore_Click_{i}";
        taskList.Add(Task.Run(() =>
        {
            if (name.Equals("btnThreadCore_Click_11"))
            {
                throw new Exception("btnThreadCore_Click_11异常");
            }
            else if (name.Equals("btnThreadCore_Click_12"))
            {
                throw new Exception("btnThreadCore_Click_12异常");
            }
            else if (name.Equals("btnThreadCore_Click_38"))
            {
                throw new Exception("btnThreadCore_Click_38异常");
            }
            Console.WriteLine($"This is {name}成功 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }));
    }
    //多线程里面抛出的异常,会终结当前线程;但是不会影响别的线程;
    //那线程异常哪里去了? 被吞了,
    //假如我想获取异常信息,还需要通知别的线程
    Task.WaitAll(taskList.ToArray());//1 可以捕获到线程的异常
}
catch (AggregateException aex)//2 需要try-catch-AggregateException
{
    foreach (var exception in aex.InnerExceptions)
    {
        Console.WriteLine(exception.Message);
    }
}
catch (Exception ex)//可以多catch  先具体再全部
{
    Console.WriteLine(ex);
}
//线程异常后经常是需要通知别的线程,而不是等到WaitAll,问题就是要线程取消
//工作中常规建议:多线程的委托里面不允许异常,包一层try-catch,然后记录下来异常信息,完成需要的操作

线程取消:

                //多线程并发任务,某个失败后,希望通知别的线程,都停下来,how?
                //Thread.Abort--终止线程;向当前线程抛一个异常然后终结任务;线程属于OS资源,可能不会立即停下来
                //Task不能外部终止任务,只能自己终止自己(上帝才能打败自己)

                //cts有个bool属性IsCancellationRequested 初始化是false
                //调用Cancel方法后变成true(不能再变回去),可以重复cancel
                try
                {
                    CancellationTokenSource cts = new CancellationTokenSource();
                    List<Task> taskList = new List<Task>();
                    for (int i = 0; i < 50; i++)
                    {
                        string name = $"btnThreadCore_Click_{i}";
                        taskList.Add(Task.Run(() =>
                        {
                            try
                            {
                                if (!cts.IsCancellationRequested)
                                    Console.WriteLine($"This is {name} 开始 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");

                                Thread.Sleep(new Random().Next(50, 100));

                                if (name.Equals("btnThreadCore_Click_11"))
                                {
                                    throw new Exception("btnThreadCore_Click_11异常");
                                }
                                else if (name.Equals("btnThreadCore_Click_12"))
                                {
                                    throw new Exception("btnThreadCore_Click_12异常");
                                }
                                else if (name.Equals("btnThreadCore_Click_13"))
                                {
                                    cts.Cancel();
                                }
                                if (!cts.IsCancellationRequested)
                                {
                                    Console.WriteLine($"This is {name}成功结束 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                }
                                else
                                {
                                    Console.WriteLine($"This is {name}中途停止 ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
                                    return;
                                }
                            }
                            catch (Exception ex)
                            {
                                Console.WriteLine(ex.Message);
                                cts.Cancel();
                            }
                        }, cts.Token));
                    }
                    //1 准备cts  2 try-catch-cancel  3 Action要随时判断IsCancellationRequested
                    //尽快停止,肯定有延迟,在判断环节才会结束

                    Task.WaitAll(taskList.ToArray());
                    //如果线程还没启动,能不能就别启动了?
                    //1 启动线程传递Token  2 异常抓取
                    //在Cancel时还没有启动的任务,就不启动了;也是抛异常,cts.Token.ThrowIfCancellationRequested
                }
                catch (AggregateException aex)
                {
                    foreach (var exception in aex.InnerExceptions)
                    {
                        Console.WriteLine(exception.Message);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                }

临时变量:

 for (int i = 0; i < 5; i++)
 {
     Task.Run(() =>
     {
         Console.WriteLine($"This is btnThreadCore_Click_{i} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
     });
 }

为什么运行结果后,都是5呢?

  临时变量问题,线程是非阻塞的,延迟启动的;线程执行的时候,i已经是5了

那么该如何解决呢?

  每次都声明一个变量k去接收,k是闭包里面的变量,每次循环都有一个独立的k,5个k变量  1个i变量

for (int i = 0; i < 5; i++)
{
    int k = i;
    Task.Run(() =>
    {
        Console.WriteLine($"This is btnThreadCore_Click_{i}_{k} ThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    });
}

这样再运行,结果就正常了。

 线程安全&lock:

线程安全:如果你的代码在进程中有多个线程同时运行这一段,如果每次运行的结果都跟单线程运行时的结果一致,那么就是线程安全的

线程安全问题一般都是有全局变量/共享变量/静态变量/硬盘文件/数据库的值,只要多线程都能访问和修改

发生是因为多个线程相同操作,出现了覆盖,怎么解决?

1 Lock解决多线程冲突

Lock是语法糖,Monitor.Enter,占据一个引用,别的线程就只能等着

推荐锁是private static readonly object,

A不能是Null,可以编译不能运行;

B 不推荐lock(this),外面如果也要用实例,就冲突了

//Test test = new Test();
//Task.Delay(1000).ContinueWith(t =>
//{
//    lock (test)
//    {
//        Console.WriteLine("*********Start**********");
//        Thread.Sleep(5000);
//        Console.WriteLine("*********End**********");
//    }
//});
//test.DoTest();

//C 不应该是string; string在内存分配上是重用的,会冲突
//D Lock里面的代码不要太多,这里是单线程的
Test test = new Test();
string student = "水煮鱼";
Task.Delay(1000).ContinueWith(t =>
{
    lock (student)
    {
        Console.WriteLine("*********Start**********");
        Thread.Sleep(5000);
        Console.WriteLine("*********End**********");
    }
});
test.DoTestString();
//2 线程安全集合
//System.Collections.Concurrent.ConcurrentQueue<int>

//3 数据分拆,避免多线程操作同一个数据;又安全又高效

for (int i = 0; i < 10000; i++)
{
    this.iNumSync++;
}
for (int i = 0; i < 10000; i++)
{
    Task.Run(() =>
    {
        lock (Form_Lock)//任意时刻只有一个线程能进入方法块儿,这不就变成了单线程
        {
            this.iNumAsync++;
        }
    });
}
for (int i = 0; i < 10000; i++)
{
    int k = i;
    Task.Run(() => this.iListAsync.Add(k));
}

Thread.Sleep(5 * 1000);
Console.WriteLine($"iNumSync={this.iNumSync} iNumAsync={this.iNumAsync} listNum={this.iListAsync.Count}");
//iNumSync 和  iNumAsync分别是多少   9981/9988  1到10000以内

原文地址:https://www.cnblogs.com/lonelyxmas/p/11560217.html

时间: 2024-10-12 14:51:00

Threads(异步和多线程)的相关文章

State Threads——异步回调的线性实现

State Threads——异步回调的线性实现 原文链接:http://coolshell.cn/articles/12012.html 本文的标题看起来有点拗口,其实State Threads库就是在单线程中使用同步编程思想来实现异步的处理流程,从而实现单线程能并发处理成百上千个请求,而且每个请求的处理过程是线性的,没有使用晦涩难懂的callback机制来衔接处理流程. ST (State Threads) 库提供了一种高性能.可扩展服务器(比如web server.proxy server

C#中异步和多线程的区别

C#中异步和多线程的区别是什么呢?异步和多线程两者都可以达到避免调用线程阻塞的目的,从而提高软件的可响应性.甚至有些时候我们就认为异步和多线程是等同的概念.但是,异步和多线程还是有一些区别的.而这些区别造成了使用异步和多线程的时机的区别. 异步和多线程的区别之异步操作的本质 所有的程序最终都会由计算机硬件来执行,所以为了更好的理解异步操作的本质,我们有必要了解一下它的硬件基础. 熟悉电脑硬件的朋友肯定对DMA这个词不陌生,硬盘.光驱的技术规格中都有明确DMA的模式指标,其实网卡.声卡.显卡也是有

同步、异步、多线程与事件型综述

转自:http://blog.csdn.net/chszs/article/details/8867174 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs首先要了解什么是阻塞和阻塞式IO.线程在执行中如果遇到磁盘读写或网络通信(统称IO操作),通常要耗费较长的时间,这时操作系统会剥夺此线程的CPU控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞.当IO操作完毕时,操作系统将这个线程的阻塞状态解除,恢复其对CPU的控制权,令

PHP中实现异步调用多线程程序代码

本文章详细的介绍了关于PHP中实现异步调用多线程方法,下面我们以给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送来讲述. 比如现在有一个场景,给1000个用户发送一封推荐邮件,用户输入或者导入邮件账号了提交服务器执行发送 第一种解决方法: 代码如下: <?php $count=count($emailarr); for($i=0;$i<$count;$i ) { sendmail(.....);//发送邮件 } ?>              这段代码用户体

关于异步和多线程的关系

个人的理解是这样的: 1.  异步通信的意思是,当A发送完消息之后,不等待B的回应,继续执行之后的程序.在将来的某个时刻,A再来检查是否收到B的回应. 异步就是彼此独立,在等待某事件的过程中继续做自己的事,不需要等待这一事件完成后再工作. 2. 多线程是程序设计的逻辑层概念,它是进程中并发运行的一段代码.多线程可以实现线程间的切换执行. 3. 异步和同步是相对的,同步就是顺序执行,执行完一个再执行下一个,需要等待.协调运行.线程就是实现异步的一个方式.异步是让调用方法的主线程不需要同步等待另一线

并发和并行,异步与多线程区别

1.并发和并行的区别 可由上图形象指出两者的区别: 1)定义: 并发:在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行. 并行:在操作系统中,一组程序按独立异步的速度执行,无论从微观还是宏观,程序都是一起执行的. 来个比喻:并发和并行的区别就是一个人同时吃三个馒头和三个人同时吃三个馒头: 在单CPU系统中,系统调度在某一时刻只能让一个线程运行,虽然这种调试机制有多种形式(大多数是时间片轮巡为主

IOS异步和多线程操作&amp;&amp;在sqlite3中的应用

1,数据库I/O操作(异步) 数据库本身是存储在磁盘上.访问和修改数据库,即对磁盘进行读写,即I/O操作. 磁盘属于计算机硬件,具有DMA能力,不需要CPU干预,可以实现异步操作. I/O操作一般是消耗时间,sqlite使用异步处理I/O操作. 当有多个事务对数据库进行操作,对应,也会有多个I/O操作. 操作系统将I/O操作,合理放入一个I/O队列.一次性将队列内的I/O操作提交给磁盘系统,并行处理多个I/O,提高效率.本人也没有特别深入研究. 2,异步和多线程 异步和多线程,都有能力实现,不阻

程序优化方案(一) 代码优化之异步、多线程、缓存

上部分说了代码的合并,那么接下来就说说如何真正的提升程序的速度.这里不得不提到的几个优化程序速度的技术就是异步,多线程和缓存.首先我们要知道程序为什么会卡,特别是加载的时候很慢,原因在于之前的程序是单线程,中途大量的数据库操作和外部接口的调用都耗用了大量的时间导致方法堵塞,所以界面上表现的就是假死状态. 那么异步加多线程加缓存用对地方刚好可以解决这些问题,那么这三种东西都是在什么情况下使用呢. 一.异步 假如现在我们主程序上有一个Grid,当我们在加载的时候这个Grid需要绑定大量的数据,并且根

异步、多线程解惑

关于多线程和异步相信不少人都有疑问,它们之间到底有什么区别呢? 从知乎上看见一个比喻还是比较好理解的: 现在假设整个城市就只有1个火车,1个售票员,每个乘客咨询售票员后需要思考1分钟再决定买哪趟车的票. 异步 在买票的人咨询后,需要思考1分钟,马上靠边站,但不用重新排队,什么时候想清楚可以立马去跟售票员去买票.在该人站在旁边思考的时候,后面的人赶紧上去接着买.这时候队伍是很快的挪动的,没有阻塞,售票员的最大化的效率. 多线程 火车站开n个窗口(但还是只有一个人售票),外面同时排n个队,售票员回答