欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Threads(异步和多线程)

程序员文章站 2022-04-15 08:17:10
Task是.NET Framework3.0出现的,线程是基于线程池的,然后提供丰富的api,Thread方法很多很强大,但是太过强大,没有限制。 DoSomethingLong方法如下: /// /// 一个比较耗时耗资源的私有方法 /// ///

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"));
}

Threads(异步和多线程)

 

 

 如果这样去调用:

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);
    });
}

 

Threads(异步和多线程)

 

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

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);
    });
}

运行结果如下:

 Threads(异步和多线程)

 

 

 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}");
}

运行结果如下:

  Threads(异步和多线程)

 

 如果将最后一个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}");
 }

Threads(异步和多线程)

 

 Threads(异步和多线程)

 

什么时候用多线程?

  任务并发是时候

多线程能干嘛?

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

 

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

 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")));

Threads(异步和多线程)

 

 现在要求,谁第一个完成,获得红包奖励(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个模块全部完成后,集中点评");

Threads(异步和多线程)

 

   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")}");
     });
 }

Threads(异步和多线程)

 

 为什么运行结果后,都是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")}");
    });
}

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

Threads(异步和多线程)

 

 线程安全&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以内