首页 > 作文

详解C#异步多线程使用中的常见问题

更新时间:2023-04-04 12:48:51 阅读: 评论:0

异常处理

小伙伴有没有想过,多线程的异常怎么处理,同步方法内的异常处理,想必都非常非常熟悉了。那多线程是什么样的呢,接着我讲解多线程的异常处理

首先,我们定义个任务列表,当 11、12 次的时候,抛出一个异常,最外围使用 try catch 包一下

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    try    {        taskfactory taskfactory = new taskfactory();        list<task> tasks = new list<task>();        for (int i = 0; i < 20; i++)        {            string name = $"第 {i} 次";            action<object> action = t =>            {                thread.sleep(2 * 1000);                if (name.tostring().equals("第 11 次"))                {                    throw new exception($"{t},执行失败");                }                if (name.tostring().equals("第 12 次"))                {                    throw new exception($"{t},执行失败");                }                console.writeline($"{t},执行成功");            };            tasks.add(taskfactory.startnew(action, name));        }    }    catch (aggregateexception aex)    {        foreach (var item in aex.innerexceptions)        {            console.writeline("main aggregateexception:" + item.message);        }    }    catch (exception ex)    {        console.writeline("main exception:" + ex.message);    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到 vs 捕获到了异常的代码行,但 catch 并未捕获到异常,这是为什么呢?是因为线程里面的异常被吞掉了,从运行的结果也可以看到,main end 在子线程没有执行任时就已经结束了,那说明 catch 已经执行过去了。

那有没有办法捕获多线程的异常呢?答案:有的,等待线程完成计算即可

看下面代码,有个特殊的地方 aggregateexception.innerexceptions 专门为多线程准备的,可以查看多线程异常信息

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    try    {        taskfactory taskfactory = new taskfactory();        list<task> tasks = new list<task>();        for (int i = 0; i < 20; i++)        {            string name = $"第 {i} 次";            action<object> action = t =>            {                thread.sleep(2 * 1000);                if (name.tostring().equals("第 11 次"))                {                    throw new exception($"{t},执行失败");                }                if (name.tostring().equals("第 12 次"))                {                    throw new exception($"{t},执行失败");                }                console.writeline($"{t},执行成功");            };            tasks.add(taskfactory.startnew(action, name)); 形容有内涵的成语       }        task.waitall(tasks.toarray());    }    catch (aggregateexception aex)    {        foreach (var item in aex.innerexceptions)        {            console.writeline("main aggregateexception:" + item.message);        }    }    catch (exception ex)    {        console.writeline("main exception:" + ex.message);    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动线程,可以看到任务全部执行完毕,且 aggregateexception.innerexceptions 存储了,子线程执行时的异常信息

但 waitall 不好,总不能一直 waitall 吧,它会卡界面。并不适用于异步场景对吧,接着来看另外一直解决方案。就是子线程里不允许出现异常,如果有自己处理好,即 try catch 包一下,平时工作中建议这么做。

使用 try catch 将子线程执行的代码包一下,且在 catch 打印错误信息

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    try    {        taskfactory taskfactory = new taskfactory();        list<task> tasks = new list<task>();        for (int i = 0; i < 20; i++)        {            string name = $"第 {i} 次";            action<object> action = t =>            {                try                {                    thread.sleep(2 * 1000);                    if (name.tostring().equals("第 11 次"))                    {                        throw new exception($"{t},执行失败");                    }                    if (name.tostring().equals("第 12 次"))                    {                        throw new exception($"{t},执行失败");                    }                    console.writeline($"{t},执行成功");                }                catch (exception ex)                {console.writeline(ex.message);                }            };            tasks.add(taskfactory.startnew(action, name));        }    }    catch (aggregateexception aex)    {        foreach (var item in aex.innerexceptions)        {            console.writeline("main aggregateexception:" + item.message);        }    }    catch (exception ex)    {        console.writeline("main exception:" + ex.message);    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到任务全部执行,且子线程异常也捕获到

线程取消

有时候会有这样的场景,多个任务并发执行,如果某个任务失败了,通知其他的任务都停下来。首先打个预防针 task 在外部无法中止的,thread.abort 不靠谱。其实线程取消的这个想法是错误的,线程是 os 的资源,程序是无法掌控什么时候取消,发出一个动作可能立马取消,也可能等 1 s 取消。

解决方案:线程自己停止自己,定义公共的变量,修改变量状态,其他线程不断检测公共变量

例如:cancellationtokensource 就是公共变量,初始化为 fal 状态,程序执行 cancellationtokensource .cancel() 方法会取消,其他线程检测到 cancellationtokensource .iscancellationrequested 会是取消状态。cancellationtokensource.token 在启动 task 时传入,如果已经 cancellationtokensource.cancel() ,这个任务会放弃启动,抛出一个异常的形式放弃。

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    try    {        taskfactory taskfactory = new taskfactory();        list<task> tasks = new list<task>();        cancellationtokensource cancellationtokensource = new cancellationtokensource(); // bool         for (int i = 0; i < 20; i++)        {            string name = $"第 {i} 次";            action<object> action = t =>            {                try                {                    thread.sleep(2 * 1000);                    if (name.tostring().equals("第 11 次"))                    {                    五红水    throw new exception($"{t},执行失败");                    }                    if (name.tostring().equals("第 12 次"))                    {                        throw new exception($"{t},执行失败");                    }                    if (cancellationtokensource.iscancellationrequested) // 检测信号量                    {                        console.writeline($"{t},放弃执行");                        return;                    }                    console.writeline($"{t},执行成功");       关于交友的诗         }                catch (exception ex)                {                    cancellationtokensource.cancel();                    console.writeline(ex.message);                }            };            tasks.add(taskfactory.startnew(action, name,cancellationtokensource.token));        }        task.waitall(tasks.toarray());    }    catch (aggregateexception aex)    {        foreach (var item in aex.innerexceptions)        {            console.writeline("main aggregateexception:" + item.message);        }    }    catch (exception ex)    {        console.writeline("main exception:" + ex.message);    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到 11、12 此任务失败,18、19 放弃了任务执。有的小伙伴疑问了,12 之后的部分为什么执行成功了,因为 cpu 是分时分片的吗,会有延迟,延迟少不了。

临时变量

首先看个代码,循环 5 次,多线程的方式,依次输出序号

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    for (int i = 0; i < 5; i++)    {        task.run(() => {            console.writeline(i);        });    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,不是我们预期的结果 0、1、2、3、4,为什么是 5 个 5 呢?因为全程只有一个 i ,当主线程执行完毕时 i = 5 ,但子线程可能还没有开始执行任务,轮到子线程取 i 时,已经是主线程 1 循环完毕后的 5 了。

改造代码:在 for 循环内加一行代码 int k = i,且在子线程用的变量也改为 k

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    for (int i = 0; i < 5; i++)    {        int k = i;        task.run(() => {            console.writeline($"k={k},i={i}");        });    }    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到是我们预期的结果 0、1、2、3、4,为什么会这样子呢?因为全程有 5 个 k,每次循环都会创建一个 k 存储当前的 i,不同的子线程使用的也是,每次循环的 i 值。

线程安全

首先为什么会有线程安全的概念呢?首先我们来看一个正常程序,如下

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    int totalcount = 0;    list<int> vs = new list<int>();    for (int i = 0; i < 10000; i++)    {        totalcount += 1;        vs.add(i);    }    console.writeline(totalcount);    console.writeline(vs.count);    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到循环 10000 次,最终的求和与列表里的数据量都是 10000,这是正常的

接着,将求和与添加列表,换成多线程,等待全部线程完成工作后,打印信息

static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    int totalcount = 0;    list<int> vs = new list<int>();    taskfactory taskfactory = new taskfactory();  开方怎么算  list<task> tasks = new list<task>();    for (int i = 0; i < 10000; i自然现象++)    {        int k = i;        tasks.add(taskfactory.startnew(() =>        {            totalcount += 1;            vs.add(i);        }));    }    task.waitall(tasks.toarray());    console.writeline(totalcount);    console.writeline(vs.count);    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

启动程序,可以看到,两个结果都不是 10000 呢?这就是线程安全

因为 totalcount 是个共享的变量,当多个线程去取 totalcount 进行 +1 后,线程都去放值的时候,后一个线程会替换掉前一个线程放置的值,所以就会形成做最终不是 10000 的结果。列表,可以看做是一个连续的块,当多线程添加的时候,也会进行覆盖。

如何解决呢?答案:lock、安全队列、拆分合并计算。下面对 lock 进行讲解,安全队列与拆分合并计算,有兴趣的小伙伴可以私下交流

1 .lock
第一种,通过加锁的方式,这种也是日常工作总常用的一种。首先定义个私有的静态引用类型的变量,然后将需要锁的运算放到 lock () 方法内

在 { } 内同一时刻,只有一个线程执行,所以尽可能 {} 放置必要的逻辑运行提高效率。lock 只能锁引用类型,原理是占用这个引用链接。不要用 string 会享元,即如 lock() 是相同的字符串,无论定义多少个变量,其实都是一个。

internal class program{    private static readonly object _lock = new object();    static void main(string[] args)    {        console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");        int totalcount = 0;        list<int> vs = new list<int>();        taskfactory taskfactory = new taskfactory();        list<task> tasks = new list<task>();        for (int i = 0; i < 10000; i++)        {            int k = i;            tasks.add(taskfactory.startnew(() =>            {                lock (_lock)                {                    totalcount += 1;                    vs.add(i);                }            }));        }        task.waitall(tasks.toarray());        console.writeline(totalcount);        console.writeline(vs.count);        console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");        console.readline();    }}

启动程序,可以看到,此时在多线程的情况下,最终的结果是正常的

这段代码,是官方推荐写法 private 防止外面也被引用,static 保证全场唯一

private static readonly object _lock = new object();

扩展:与 lock 等价的有个 monitor,用法如下

private static object _lock = new object();static void main(string[] args){    console.writeline($"main start,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    int totalcount = 0;    list<int> vs = new list<int>();    taskfactory taskfactory = new taskfactory();    list<task> tasks = new list<task>();    for (int i = 0; i < 10000; i++)    {        int k = i;        tasks.add(taskfactory.startnew(() =>        {            monitor.enter(_lock);            totalcount += 1;            vs.add(i);            monitor.exit(_lock);        }));    }    task.waitall(tasks.toarray());    console.writeline(totalcount);    console.writeline(vs.count);    console.writeline($"main end,threadid:{thread.currentthread.managedthreadid},datetime:{datetime.now.tolongtimestring()}");    console.readline();}

到此这篇关于详解c#异步多线程使用中的常见问题的文章就介绍到这了,更多相关c#异步多线程内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

本文发布于:2023-04-04 12:48:49,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/zuowen/52c962c7aa40c977daee06f3142889dc.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

本文word下载地址:详解C#异步多线程使用中的常见问题.doc

本文 PDF 下载地址:详解C#异步多线程使用中的常见问题.pdf

标签:线程   异常   可以看到   多线程
相关文章
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图