我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C#异步多线程使用中的常见问题有哪些

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C#异步多线程使用中的常见问题有哪些

本篇内容主要讲解“C#异步多线程使用中的常见问题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#异步多线程使用中的常见问题有哪些”吧!

异常处理

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

首先,我们定义个任务列表,当 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 已经执行过去了。

C#异步多线程使用中的常见问题有哪些

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

看下面代码,有个特殊的地方 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 存储了,子线程执行时的异常信息

C#异步多线程使用中的常见问题有哪些

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

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

C#异步多线程使用中的常见问题有哪些

线程取消

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

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

例如:CancellationTokenSource 就是公共变量,初始化为 false 状态,程序执行 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 是分时分片的吗,会有延迟,延迟少不了。

C#异步多线程使用中的常见问题有哪些

临时变量

首先看个代码,循环 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 了。

C#异步多线程使用中的常见问题有哪些

改造代码:在 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 值。

C#异步多线程使用中的常见问题有哪些

线程安全

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

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,这是正常的

C#异步多线程使用中的常见问题有哪些

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

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 的结果。列表,可以看做是一个连续的块,当多线程添加的时候,也会进行覆盖。

C#异步多线程使用中的常见问题有哪些

如何解决呢?答案: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();    }}

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

C#异步多线程使用中的常见问题有哪些

这段代码,是官方推荐写法 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#异步多线程使用中的常见问题有哪些”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C#异步多线程使用中的常见问题有哪些

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C#异步多线程使用中的常见问题有哪些

本篇内容主要讲解“C#异步多线程使用中的常见问题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C#异步多线程使用中的常见问题有哪些”吧!异常处理小伙伴有没有想过,多线程的异常怎么处理,同步
2023-06-22

常见的Java多线程问题有哪些

这篇文章主要介绍常见的Java多线程问题有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!1、多线程有什么用?一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡。所谓
2023-06-02

编写多线程Java应用程序常见问题有哪些

这篇文章主要介绍“编写多线程Java应用程序常见问题有哪些”,在日常操作中,相信很多人在编写多线程Java应用程序常见问题有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”编写多线程Java应用程序常见问题
2023-06-17

JAVA有哪些常见的多线程面试题

本文小编为大家详细介绍“JAVA有哪些常见的多线程面试题”,内容详细,步骤清晰,细节处理妥当,希望这篇“JAVA有哪些常见的多线程面试题”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。1、什么是线程?线程是操作系统
2023-06-04

JAVA编程中的常见问题有哪些

本篇内容主要讲解“JAVA编程中的常见问题有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JAVA编程中的常见问题有哪些”吧!问题一:编译器找不到类。解决方法:确保你已经导入了类或者它的包。
2023-06-17

Java编程中常见的问题有哪些

本篇内容介绍了“Java编程中常见的问题有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!字符串连接误用错误的写法:String s =
2023-06-17

C#中常见的线程同步问题及解决方法

C#中常见的线程同步问题及解决方法引言:在多线程编程中,线程同步是一个关键的概念。当多个线程同时访问共享资源时,会导致数据不一致或出现竞态条件等问题。本文将介绍C#中常见的线程同步问题,并提供相应的解决方法和示例代码。一、不正确的数据共享当
2023-10-22

使用代理IP有哪些常见的问题

这篇文章主要讲解了“使用代理IP有哪些常见的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“使用代理IP有哪些常见的问题”吧!1、代理靠什么来提供代理服务?2、免费代理会截取通讯数据吗?3
2023-06-20

使用python random库的常见问题有哪些

常见问题:如何生成随机整数?使用random.randint()函数可以生成指定范围内的随机整数。如何生成随机浮点数?使用random.uniform()函数可以生成指定范围内的随机浮点数。如何生成随机字符串?使用random.choice
使用python random库的常见问题有哪些
2024-02-29

使用golang框架有哪些常见的问题?

go 框架常见问题包括依赖性管理、路由、错误处理、性能和安全性漏洞。解决方案包括使用依赖性管理工具、正确配置路由、创建定制错误处理程序、进行性能优化和启用安全功能。例如,在 gin 框架中,错误的路由规则可能会导致 404 错误,其解决方案
使用golang框架有哪些常见的问题?
2024-05-24

c#使用listbox的常见问题有哪些及怎么解决

在使用 C# 的 ListBox 控件时,可能会遇到以下几个常见问题:1. 如何向 ListBox 添加项?使用 ListBox 的 Items 属性,可以通过 Add 或者 AddRange 方法向 ListBox 添加项。2. 如何获取
2023-08-09

使用idea插件的常见问题有哪些

使用IDEA插件时可能遇到的一些常见问题有:插件无法安装或更新:有时候插件仓库无法访问或下载速度缓慢,可以尝试切换到其他插件仓库,或者手动下载插件并进行安装。插件与IDEA版本不兼容:某些插件可能只支持特定的IDEA版本,如果插件无法正常工
2023-10-22

如何解决 C++ 多线程编程中常见的死锁问题?

如何解决 c++++ 多线程编程中的常见死锁问题?避免死锁的技术:加锁顺序:始终以相同的顺序获取锁。死锁检测:使用算法检测并解决死锁。超时:为锁设置超时值,防止线程无限期等待。优先级反转:分配不同的优先级,减少死锁可能性。如何解决 C++
如何解决 C++ 多线程编程中常见的死锁问题?
2024-05-13

Winform中C#线程控制的常见情况有哪些

本篇内容主要讲解“Winform中C#线程控制的常见情况有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Winform中C#线程控制的常见情况有哪些”吧!Winform界面中,将事务放在新开
2023-06-17

使用HTTP代理IP有哪些常见的问题

这篇文章主要介绍“使用HTTP代理IP有哪些常见的问题”,在日常操作中,相信很多人在使用HTTP代理IP有哪些常见的问题问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”使用HTTP代理IP有哪些常见的问题”的疑
2023-06-20

c#使用多线程的方式有哪些

在C#中,有多种方式可以使用多线程:1. 使用Thread类:可以通过创建Thread对象,并将一个方法或委托分配给它的Start方法来创建一个新线程。例如:```csharpThread thread = new Thread(SomeM
2023-08-09

C# foreach使用中常见的错误有哪些

这篇文章主要讲解了“C# foreach使用中常见的错误有哪些”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C# foreach使用中常见的错误有哪些”吧!在做项目时经常会碰到用C# for
2023-06-17

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录