C#基础系列——多线程的常见用法

前言:前面几节分别介绍了下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 = 0; i < 1000000; i++)
       {
           Console.WriteLine("后台线程计数" + i);
           //Thread.Sleep(100);
       }
 }

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

        static void Main(string[] args)
        {
            Thread oGetArgThread = new Thread(new System.Threading.ThreadStart(() =>
            {

                for (var i = 0; i < 1000000; 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();
         }

     private static void Test2(object Count)
        {
            for (var i = 0; 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 = 0,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前。
        BelowNormal = 1,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前。
        //     默认情况下,线程具有 Normal 优先级。
        Normal = 2,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前。
        AboveNormal = 3,
        //
        // 摘要:
        //     可以将 System.Threading.Thread 安排在具有任何其他优先级的线程之前。
        Highest = 4,
    }

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

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

     private static object oLock = new object();
        private static void Test2(object Count)
        {
            lock (oLock)
            {
                for (var i = 0; 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(500);
           });
           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(250, 255);

            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(1000, 5000));
                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 < 2)
                {
                    CLogService.Instance.Debug("shit -- 3实验室中测试床Name:" + strTestBedName + "2完成异常0");

       //这里很重要的一点,每一次return 前一定要记得释放线程,否则这个一直会占用资源
                    semaphore.Release();
                    return;
                }

                var strXML = oGetRootDevicesByTestBedGIDRes.Documents[0];
                var strExeName = oGetRootDevicesByTestBedGIDRes.Documents[1];
                //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;
                    }, 600000);
                    //如果等了10分钟还在跳纤则跳出
                    if (bIsworking)
                    {
                        oRes.ErrCode = "false交换机跳纤时间超过10分钟,请检查异常再操作";
                        return oRes;
                    }

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

时间: 2024-11-07 14:18:31

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

C#基础系列——多线程的常见用法详解

前言:前面几节分别介绍了下C#基础技术中的反射.特性.泛型.序列化.扩展方法.Linq to Xml等,这篇跟着来介绍下C#的另一基础技术的使用.最近项目有点紧张,所以准备也不是特别充分.此篇就主要从博主使用过的几种多线程的用法从应用层面大概介绍下.文中观点都是博主个人的理解,如果有不对的地方望大家指正~~ 1.多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这

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

关于promise我在之前的文章已经应用过好几次,如[js高手之路]Node.js+jade+express+mongodb+mongoose+promise实现todolist,本文就来讲解下promise的常见用法. 为什么会有promise,他的作用是什么? promise主要是为了解决js中多个异步回调难以维护和控制的问题. 什么是promise? 从图中,我们可以看出,Promise是一个函数,这个函数上有在项目中常用的静态方法:all, race, reject,resolve等,原

多线程的常见用法详解 --转载

1.多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解. 2.多线程的使用: (1)最简单.最原始的使用方法:Thread oGetArgThread = new Thread(new ThreadStart(() =>{});这种用法应该大多数人都使用过,参数为一个ThreadStart类型的委托.将Threa

多线程的常见用法详解

前言:此篇就主要从博主使用过的几种多线程的用法从应用层面大概介绍下.文中观点都是博主个人的理解,如果有不对的地方望大家指正~~ 1.多线程:使用多个处理句柄同时对多个任务进行控制处理的一种技术.据博主的理解,多线程就是该应用的主线程任命其他多个线程去协助它完成需要的功能,并且主线程和协助线程是完全独立进行的.不知道这样说好不好理解,后面慢慢在使用中会有更加详细的讲解. 2.多线程的使用: (1)最简单.最原始的使用方法:Thread oGetArgThread = new Thread(new

ios多线程 -- GCD 常见用法

GCD 通信操作 #pragma mark - GCD 通信 - (void)sendMessage{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //耗时操作 [self downLoad:@"http://..."]; //回主队列刷新数据 dispatch_async(dispatch_get_main_queue(), ^{ //刷新UI操作 }); }); }

C#基础系列——再也不用担心面试官问我“事件”了

前言:作为.Net攻城狮,你面试过程中是否遇到过这样的问题呢:什么是事件?事件和委托的区别?既然事件作为一种特殊的委托,那么它的优势如何体现?诸如此类...你是否也曾经被问到过?你又是否都答出来了呢?上两篇由浅及深介绍了下委托的用法,这篇还是来说说事件.希望通过这篇的介绍,博友能有个系统的认识,至少应付面试没问题了吧.不信?瞧瞧去~~ C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序列化效率比拼 C#基础系列——反射笔记 C

C#基础系列——一场风花雪月的邂逅:接口和抽象类

前言:最近一个认识的朋友准备转行做编程,看他自己边看视频边学习,挺有干劲的.那天他问我接口和抽象类这两个东西,他说,既然它们如此相像, 我用抽象类就能解决的问题,又整个接口出来干嘛,这不是误导初学者吗.博主呵呵一笑,回想当初的自己,不也有此种疑惑么...今天打算针对他的问题,结合一个实际的使用场景来说明下抽象类和接口的异同,到底哪些情况需要用接口?又有哪些情况需要用抽象类呢? C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序

C#基础系列——委托实现简单设计模式

前言:上一篇介绍了下多线程的相关知识:C#基础系列--多线程的常见用法详解,里面就提到了委托变量.这篇简单介绍下委托的使用.当然啦,园子里面很多介绍委托的文章都会说道:委托和事件的概念就像一道坎,过了这个槛的人,觉得真是太容易了,而没有过去的人每次见到委托和事件就觉得心里发慌.确实这东西就像最开始学C语言的指针一样,令人有一种很纠结的感觉,总觉得要调用一个方法直接调用就行了,为啥非要定义一个委托时执行这个方法呢.其实在C#里面很多的技术都是为了重用和简化代码而生,委托也不例外,很多使用C#多态去

C#基础系列——异步编程初探:async和await

前言:前面有篇从应用层面上面介绍了下多线程的几种用法,有博友就说到了async, await等新语法.确实,没有异步的多线程是单调的.乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便.异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现. C#基础系列目录: C#基础系列——Linq to Xml读写xml C#基础系列——扩展方法的使用 C#基础系列——序列化效率比拼 C#基础系列——反射笔记 C#基础系列——At