前言:前面几节分别介绍了下C#基础技术中的反射特性泛型序列化扩展方法Linq to Xml等,这篇跟着来介绍下C#的另一基础技术的使用。最近项目有点紧张,所以准备也不是特别充分。此篇就主要从博主使用过的几种多线程的用法从应用层面大概介绍下。文中观点都是博主个人的理解,如果有不对的地方望大家指正~~

1、多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术。据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的。不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解。

2、多线程的使用:

(1)最简单、最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});这种用法应该大多数人都使用过,参数为一个ThreadStart类型的委托。将ThreadStart转到定义可知:

public delegate void ThreadStart();

它是一个没有参数,没有返回值的委托。所以他的使用如下:

static void Main(string[] args)
{
  Thread oGetArgThread = new Thread(new ThreadStart(Test));
oGetArgThread.IsBackground = true;
oGetArgThread.Start();

    for (var i = 0; i < 1000000; i++)
    {
      Console.WriteLine("主线程计数" + i);
      //Thread.Sleep(100);
    }

}

private static void Test()
{
for (var i = ; i < ; i++)
{
Console.WriteLine("后台线程计数" + i);
//Thread.Sleep(100);
}
}

定义一个没有参数没有返回值的方法传入该委托。当然也可以不定义方法写成匿名方法:

        static void Main(string[] args)
{
Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() =>
{ for (var i = ; i < ; i++)
{
Console.WriteLine("后台线程计数" + i);
//Thread.Sleep(100);
}
}));
oGetArgThread.IsBackground = true;
oGetArgThread.Start();

这个和上面的意义相同。得到的结果如下:

说明主线程和后台线程是互相独立的。由系统调度资源去执行。

如果这样那有人就要问了,如果我需要多线程执行的方法有参数或者有返回值或者既有参数又有返回值呢。。。别着急我们来看看new Thread()的几个构造函数:

public Thread(ParameterizedThreadStart start);
public Thread(ThreadStart start);
public Thread(ParameterizedThreadStart start, int maxStackSize);
public Thread(ThreadStart start, int maxStackSize);

转到定义可知参数有两类,一类是无参无返回值的委托,另一类是有参无返回值的委托。对于有参数的委托使用方法:

    static void Main(string[] args)
{
Thread oThread = new Thread(new ParameterizedThreadStart(Test2));
oThread.IsBackground = true;
oThread.Start(1000);
}      private static void Test2(object Count)
{
for (var i = ; i < (int)Count; i++)
{
Console.WriteLine("后台线程计数" + i);
//Thread.Sleep(100);
}
}

对于有参又有返回值的委托,很显然使用new Thread()这种方式是没有解决方案的。其实对于有参又有返回值的委托可以使用异步来实现:

public delegate string MethodCaller(string name);//定义个代理
MethodCaller mc = new MethodCaller(GetName);
string name = "my name";//输入参数
IAsyncResult result = mc.BeginInvoke(name,null, null);
string myname = mc.EndInvoke(result);//用于接收返回值 public string GetName(string name) // 函数
{
return name;
}

关于这种方式还有几点值得一说的是:

①Thread oGetArgThread = new Thread(new ThreadStart(Test));

oGetArgThread.Join();//主线程阻塞,等待分支线程运行结束,这一步看功能需求进行选择,主要为了多个进程达到同步的效果

②线程的优先级可以通过Thread对象的Priority属性来设置,Priority属性对应一个枚举:

public enum ThreadPriority
{
// 摘要:
// 可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之后。
Lowest = ,
//
// 摘要:
// 可以将 System.Threading.Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
BelowNormal = ,
//
// 摘要:
// 可以将 System.Threading.Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。
// 默认情况下,线程具有 Normal 优先级。
Normal = ,
//
// 摘要:
// 可以将 System.Threading.Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
AboveNormal = ,
//
// 摘要:
// 可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之前。
Highest = ,
}

从0到4,优先级由低到高。

③关于多个线程同时使用一个对象或资源的情况,也就是线程的资源共享,为了避免数据紊乱,一般采用.Net悲观锁lock的方式处理。

     private static object oLock = new object();
private static void Test2(object Count)
{
lock (oLock)
{
for (var i = ; i < (int)Count; i++)
{
Console.WriteLine("后台线程计数" + i);
//Thread.Sleep(100);
}
}
}

(2)Task方式使用多线程:这种方式一般用在需要循环处理某项业务并且需要得到处理后的结果。使用代码如下:

List<Task> lstTaskBD = new List<Task>();
foreach (var bd in lstBoards)
{
var bdTmp = bd;//这里必须要用一个临时变量
var oTask = Task.Factory.StartNew(() =>
{
var strCpBdCmd = "rm -Rf " + bdTmp.Path + "/*;cp -R " + CombineFTPPaths(FTP_EMULATION_BD_ROOT, "bd_correct") + "/* " + bdTmp.Path + "/";
oPlink.Run(bdTmp.EmulationServer.BigIP, bdTmp.EmulationServer.UserName, bdTmp.EmulationServer.Password, strCpBdCmd);
Thread.Sleep();
});
lstTaskBD.Add(oTask);
}
Task.WaitAll(lstTaskBD.ToArray());//等待所有线程只都行完毕

使用这种方式的时候需要注意这一句 var bdTmp = bd;这里必须要用一个临时变量,要不然多个bd对象容易串数据。如果有兴趣可以调试看看。这种方法比较简单,就不多说了。当然Task对象的用法肯定远不止如此,还涉及到任务的调度等复杂的逻辑。博主对这些东西理解有限,就不讲解了。

(3)线程池的用法:一般由于考虑到服务器的性能等问题,保证一个时间段内系统线程数量在一定的范围,需要使用线程池的概念。大概用法如下:

  public class CSpiderCtrl
{      //将线程池对象作为一个全局变量
static Semaphore semaphore; public static void Run()
{
//1. 创建 SuperLCBB客户端对象
var oClient = new ServiceReference_SuperLCBB.SOAServiceClient();        //2.初始化的时候new最大的线程池个数255(这个数值根据实际情况来判断,如果服务器上面的东西很少,则可以设置大点)
semaphore = new Semaphore(, ); CLogService.Instance.Debug("又一轮定时采集..."); _TestBedGo(oClient); }    //执行多线程的方法    private static void _TestBedGo(ServiceReference_SuperLCBB.SOAServiceClient oClient)
{
List<string> lstExceptPDUs = new List<string>(){
"SUPERLABEXP"
};
var oTestBedRes = oClient.GetTestBedExceptSomePDU(lstExceptPDUs.ToArray(), true);
if (CKVRes.ERRCODE_SUCCESS != oTestBedRes.ErrCode)
{
CLogService.Instance.Error("xxx");
return;
} var lstTestBed = oTestBedRes.ToDocumentsEx(); System.Threading.Tasks.Parallel.ForEach(lstTestBed, (oTestBed) =>
{          //一次最多255个线程,超过255的必须等待线程池释放一个线程出来才行
semaphore.WaitOne(); //CLogService.Instance.Info("开始采集测试床:" + oTestBed[TBLTestBed.PROP_NAME]);
//Thread.Sleep(2000); var strTestBedName = oTestBed[TBLTestBed.PROP_NAME] as string;
var strSuperDevIP = oTestBed[TBLTestBed.PROP_SUPERDEVIP] as string;
var strTestBedGID = oTestBed[TBLTestBed.PROP_GID] as string;
var strPdu = oTestBed[TBLTestBed.PROP_PDUGID] as string;
Thread.Sleep(new Random().Next(, ));
var oGetRootDevicesByTestBedGIDRes = oClient.GetRootDevicesByTestBedGID(strTestBedGID);
CLogService.Instance.Debug(strPdu + "——测试床Name:" + strTestBedName + "开始");
Stopwatch sp = new Stopwatch();
sp.Start();
if (oGetRootDevicesByTestBedGIDRes.ErrCode != CKVRes.ERRCODE_SUCCESS || oGetRootDevicesByTestBedGIDRes.Documents.Count < )
{
CLogService.Instance.Debug("shit -- 3实验室中测试床Name:" + strTestBedName + "2完成异常0");        //这里很重要的一点,每一次return 前一定要记得释放线程,否则这个一直会占用资源
semaphore.Release();
return;
} var strXML = oGetRootDevicesByTestBedGIDRes.Documents[];
var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[];
//var strExeName = "RateSpider"; var oSuperDevClient = new SuperDevClient(CSuperDev.ENDPOINT, string.Format(CSuperDev.SuperDevURL, strSuperDevIP));
try
{
oSuperDevClient.IsOK();
}
catch (Exception)
{
CLogService.Instance.Error("测试床Name:" + strTestBedName + "异常,插件没起");
semaphore.Release();
return;
} //2.3.1.请求SuperDev.Server(SuperDevIP),发送Run(XML和Exename)
var oRunExeRes = new CKVRes();
try
{
oRunExeRes = oSuperDevClient.RunExeEx(strExeName, false, new string[] { strXML });
}
catch
{
//CLogService.Instance.Debug("测试床Name:" + strTestBedName + "异常:" + ex.Message);
}
sp.Stop();
CLogService.Instance.Debug(strPdu + "——测试床Name:" + strTestBedName + "完成时间" + sp.Elapsed);           //每一个线程完毕后记得释放资源
semaphore.Release();
});
} }

需要注意:Semaphore对象的数量需要根据服务器的性能来设定;System.Threading.Tasks.Parallel.ForEach这种方式表示同时启动lstTestBed.Length个线程去做一件事情,可以理解为

foreach(var oTestbed in lstTestBed)
{
Thread oThread=new Thread(new ThreadStart({ ...}));
}

(4) 多线程里面还有一个值得一说的SpinWait类,用于提供对基于自旋的等待的支持。也就是说支持重复执行一个委托,知道满足条件就返回,我们来看它的用法:

        public static void SpinUntil(Func<bool> condition);

        public static bool SpinUntil(Func<bool> condition, int millisecondsTimeout);

        public static bool SpinUntil(Func<bool> condition, TimeSpan timeout);

这个方法有三个构造函数,后两个需要传入一个时间,表示如果再规定的时间内还没有返回则自动跳出,防止死循环。

            SpinWait.SpinUntil(() =>
{
bIsworking = m_oClient.isworking(new isworking()).result;
return bIsworking == false;
}, );
//如果等了10分钟还在跳纤则跳出
if (bIsworking)
{
oRes.ErrCode = "false交换机跳纤时间超过10分钟,请检查异常再操作";
return oRes;
}

博主使用过的多线程用法大概就这么三大类,当然这些其中还涉及很多细节性的东西,博主原来使用这些的时候经常出现各种莫名的问题,可能还是没用好的原因,对这些东西理解还不够深刻。如果大家也遇到类似的问题可以拿出来探讨!!

多线程用法示例

C#基础系列——多线程的常见用法详解的更多相关文章

  1. [js高手之路] es6系列教程 - promise常见用法详解(resolve,reject,catch,then,all,race)

    关于promise我在之前的文章已经应用过好几次,如[js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist,本文就来讲解下pro ...

  2. STL pair 常见用法详解

    <算法笔记>学习笔记 pair 常见用法详解 //pair是一个很实用的"小玩意",当想要将两个元素绑在一起作为一个合成元素, //又不想因此定义结构体时,使用pair ...

  3. STL stack 常见用法详解

    <算法笔记>学习笔记 stack 常见用法详解 stack翻译为栈,是STL中实现的一个后进先出的容器.' 1.stack的定义 //要使用stack,应先添加头文件#include &l ...

  4. STL priority_queue 常见用法详解

    <算法笔记>学习笔记 priority_queue 常见用法详解 //priority_queue又称优先队列,其底层时用堆来实现的. //在优先队列中,队首元素一定是当前队列中优先级最高 ...

  5. STL queue 常见用法详解

    <算法笔记>学习笔记 queue 常见用法详解 queue翻译为队列,在STL中主要则是实现了一个先进先出的容器. 1. queue 的定义 //要使用queue,应先添加头文件#incl ...

  6. STL map 常见用法详解

    <算法笔记>学习笔记 map 常见用法详解 map翻译为映射,也是常用的STL容器 map可以将任何基本类型(包括STL容器)映射到任何基本类型(包括STL容器) 1. map 的定义 / ...

  7. STL string 常见用法详解

    string 常见用法详解 1. string 的定义 //定义string的方式跟基本数据类型相同,只需要在string后跟上变量名即可 string str; //如果要初始化,可以直接给stri ...

  8. STL set 常见用法详解

    <算法笔记>学习笔记 set 常见用法详解 set是一个内部自动有序且不含重复元素的容器 1. set 的定义 //单独定义一个set set<typename> name: ...

  9. STL vector常见用法详解

    <算法笔记>中摘取 vector常见用法详解 1. vector的定义 vector<typename> name; //typename可以是任何基本类型,例如int, do ...

随机推荐

  1. iOS开发--引用计数与ARC

    以下是关于内存管理的学习笔记:引用计数与ARC. iOS5以前自动引用计数(ARC)是在MacOS X 10.7与iOS 5中引入一项新技术,用于代替之前的手工引用计数MRC(Manual Refer ...

  2. __block 和 __weak的区别

    Blocks理解: Blocks可以访问局部变量,但是不能修改 如果修改局部变量,需要加__block __block int multiplier = 7; int (^myBlock)(int) ...

  3. Xcode 创建.a和framework静态库(转载)

    库介绍 库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种. iOS中的静态库有 .a 和 .framework两种形式:动态库有.dylib 和 .framew ...

  4. 初识angularjs

    1,angular的ng-model带来了双向绑定机制 2,用angular的表达式{{...}}现实在HTML中,存储在我们的$scope上 3,在angular中$scope是连接controll ...

  5. SQL SERVER 属性OWNER不可用于数据库xxx。该对象可能没有此属性,也可能是访问权限不足而无法检索。

    今天遇到一个案例:右键单击数据库的属性时出现下面错误提示: 属性Owner不可用于数据库xxx,该对象可能没有此属性,也可能是访问权限不足而无法检索. 使用脚本查看该数据库的Owner时发现Owner ...

  6. WPF 自定义雷达图

    自定义雷达图表如下: Git下载地址:https://github.com/Kybs0/RadarChartControl 1.创建UserControl,名为“RadarChartControl” ...

  7. sql 计算周围公里语句

    $lng2 = set_post('lng'); //当前位置坐标 $lat2 = set_post('lat'); round((*ATAN2(SQRT(SIN((`gps`.`gps_lat`-$ ...

  8. Java基础知识笔记(一:修饰词、向量、哈希表)

    一.Java语言的特点(养成经常查看Java在线帮助文档的习惯) (1)简单性:Java语言是在C和C++计算机语言的基础上进行简化和改进的一种新型计算机语言.它去掉了C和C++最难正确应用的指针和最 ...

  9. microstrip patch antenna

    微带天线有很多优点,低成本,易于集成,加工简单,缺点有:效率低.功率容易低.高Q值 ,可能存在表面波,极化效果差,弱电扫性能.带宽窄.微带线的金属厚度t远远小于波长(t << λ0 ,自由 ...

  10. Bootstrap CSS 栅格、代码和表格

    一.bootstrap栅格 Bootstrap 提供了一套响应式.移动设备优先的流式网格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列. Bootstrap 网格系统(G ...