学习过程:C#多线程的写法有很多,网上都能搜到,各有优劣。作为新手,最讨厌网上那种类似“多线程的5种方式”之类的文章,我不可能5中方式都使用一遍,我只需要其中一种最合适的。
代老师推荐向我Task类。下面是我个人对它的理解:
1、编程效率高,但损失性能
Task是Thread的升级版,会更好用。编程方便,代码量少。Task会帮你管理线程,程序员只要把握好什么时候启动线程,其他不需要操心。什么时候关闭线程、线程是否是后台线程等都是系统自己处理。强大的功能,肯定会降低效率。大部分程序不用考虑这点微乎其微的性能损失。大型互联网项目,一般也不会使用C#去写。就像初学try{}catch{}的时候,也说它会降低性能,但是项目中,几乎每个方法都写了try{}catch{},用来记录报错Log,性能影响微乎其微,可以忽略不计。
下面是2中Task启动线程的常用写法:
//写法1:
Task.Run(TestShow/*无参方法名称*/);
//方法2:
Task.Run(() =>
{
/*
* 代码块
*/
});
写法1,适用于调用单个无参方法;
写法2,适用于调用多行代码块;
2、线程启动有延迟
线程启动有几十毫秒时间延迟,并不是立即启动。下面是测试代码和结果:
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
Stopwatch sw1 = new Stopwatch();
sw1.Start();
Task.Run(() =>
{
Stopwatch sw2 = new Stopwatch();
sw2.Start();
//线程代码主体
sw2.Stop();
Console.WriteLine("执行线程耗时:" + sw2.ElapsedMilliseconds);
});
sw1.Stop();
Console.WriteLine("Task启动耗时:" + sw1.ElapsedMilliseconds);
Console.ReadKey();
}
}
}
执行代码的耗时,取决于代码块的内容。但是启动线程需要花27ms(这个时间不是固定的,跟设备、时机等多种因素相关。)
3、线程启动顺序随机
主线程按顺序写了9 个线程,代码执行的过程是按顺序执行了线程启动的9行代码。惯性思维,线程是顺序启动的。实际情况却不是,线程启动的顺序随机,下面是测试代码和结果:
using System;
using System.Threading.Tasks;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
for (int i = 0; i < 9; i++)
{
Task.Run(TestShow);
}
Console.ReadKey();
}
static int k = 0;
private static void TestShow()
{
k++;
Console.WriteLine("线程:" + k);
}
}
}
这点很重要,有个项目,我想记录与PLC通讯的所有信息记录下来,又不想损耗PLC的读写效率,想到使用的Task多线程执行记录的信息,然而时间不是按顺序的记录的,查找问题异常困难,当场裂开。然后,代老师告诉我,Task的执行顺序是随机的,如果对顺序有要求,可以使用队列。
4、数据不安全
看上述范例,按照设想应该出现1~9的随机数,但是却少了一个1,多了一个3。具体怎么解决,我还没查到,代老师在居家隔离,并且在开会,他告诉我使用Interlocked,但我查了网上资料,没搞出来,下次再说。上述范例比较极端,正常人也不会让同一个任务同时被多个线程调用。
5、不用手动结束线程
多线程有2个结束方式:
a、线程代码运行结束,线程自动结束并自动销毁;
b、手动中止并销毁;
写代码时,尽可能避免手动中止。没有死循环的代码,会按照方式a的操作,执行完成后结束线程。如果在线程中出现了while等死循环,尽量用变量控制它自然结束,按照方式a的操作,执行完成后结束线程。