• [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(三) 利用多线程提高程序性能(下)


    [.net 面向对象程序设计进阶] (18) 多线程(Multithreading)(二) 利用多线程提高程序性能(下)

    本节导读:

    上节说了线程同步中使用线程锁和线程通知的方式来处理资源共享问题,这些是多线程的基本原理。

    .NET 4.0以后对多线程的实现变得更简单了。

    本节主要讨论.NET4.0多线程的新特性——使用Task类创建多线程。

    读前必备:

    A. LINQ使用  [.net 面向对象编程基础] (20) LINQ使用

    B. 泛型          [.net 面向对象编程基础] (18) 泛型

    1. 线程池ThreadPool 

    在介绍4.0以后的多线程新特征之前,先简单说一下线程池。

    通过前面对多线程的学习,我们发现多线程的创建和使用并不难,难的在于多线程的管理,特别是线程数量级很多的情况下,如何进行管理和资源释放。需要使用线程池来解决。

    简单来说线程池就是.NET提供的存放线程的一个对象容器。

    线程池线程分为两类:工作线程和IO线程.
    线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。

    对于线程池,可使用要运行的过程的委托调用 System.Threading.ThreadPool.QueueUserWorkItem(System.Threading.WaitCallback) 方法

    下面是一个线程池的示例:

    先设置一个创建线程总数静态字段:

     static readonly int totalThreads = 20;

    使用线程池创建线程:

    //设置最小线程和最大线程数
    ThreadPool.SetMinThreads(2, 2);
    ThreadPool.SetMaxThreads(20, 20);
    
    for (int i = 0; i < totalThreads; i++)
    {
        ThreadPool.QueueUserWorkItem(o =>
        {
            Thread.Sleep(1000);
            int a, b;
            ThreadPool.GetAvailableThreads(out a, out b);
            Console.WriteLine(string.Format("({0}/{1}) #{2} : {3}", a, b, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString()));
        });
    }
    Console.WriteLine("主线程完成");

    运行结果如下:

     

    2. Task

    ThreadPoolQueueUserWorkItem()方法发起一次异步的线程执行很简单,但是该方法最大的问题是没有一个内建的机制让你知道操作什么时候完成,有没有一个内建的机制在操作完成后获得一个返回值。为此,在.NET 4.0 以后,我们可以使用System.Threading.Tasks中的Task类。这也是.NET 4.0以后多线程的推荐做法。

    构造一个Task<T>对象,并为泛型T参数传递一个操作的返回类型。

    Task类可以使用多种方法创建多线程,下面详细介绍。 

    2.1 使用Factory属性 

    Task 实例可以用各种不同的方式创建。 最常见的方法是使用任务的 Factory 属性检索可用来创建用于多个用途的TaskFactory 实例。

     例如,要创建运行操作的 Task,可以使用工厂的 StartNew 方法:          

    //最简单的线程示例
    Task.Factory.StartNew(() =>
    {
        Console.WriteLine("我是使用Factory属性创建的线程");
    });

    运行结果如下:

    如果想简单的创建一个Task,那么使用Factory.NewStart()来创建,很简便。

    如果像对所创建的Task附加更多的定制和设置特定的属性,请继续往下看。

    2.2 使用Task实例实现多线程 

    //简单的Task实例创建线程
    Action<object> action = (object obj) =>
    {
        Console.WriteLine("Task={0}, obj={1}, Thread={2}", Task.CurrentId, obj.ToString(), Thread.CurrentThread.ManagedThreadId);
    };
    Task t1 = new Task(action, "参数");
    t1.Start();

     运行结果如下:

     

    //简写上面实例,并创建100个线程
    System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
    int m = 100;
    Task[] tasks = new Task[m];
    for (int i = 0; i < m; i++)
    {
        tasks[i] = new Task((object obj) =>
            {
                Thread.Sleep(200);
                Console.WriteLine("Task={0}, obj={1}, Thread={2},当前时间:{3}",
                Task.CurrentId, obj.ToString(),
                Thread.CurrentThread.ManagedThreadId,
                System.DateTime.Now.ToString());
            }, "参数" + i.ToString()
        );
        tasks[i].Start();
    }
               
    Task.WaitAll(tasks);
    Console.WriteLine("线程耗时:{0},当前时间:{1}" ,watch.ElapsedMilliseconds,System.DateTime.Now.ToString());

    运行结果如下:

     2.3 Task传入参数

    上面介绍了使用一个Action委托来完成任程,那么给线程中传入参数,就可以使用System.Action<object>来完成。

     传入一个参数的示例:

    /// <summary>
    /// 一个参数的方法
    /// </summary>
    /// <param name="parameter"></param>
    static void MyMethod(string parameter)
    {
        Console.WriteLine("{0}", parameter);
    }

    调用如下:

    //Task传入一个参数
    Task myTask = new Task((parameter) => MyMethod(parameter.ToString()), "aaa");
    myTask.Start();

    运行结果如下:

     传入多个参数如下:

    /// <summary>
    /// 多个参数的方法
    /// </summary>
    /// <param name="parameter1"></param>
    /// <param name="parameter2"></param>
    /// <param name="parameter3"></param>
    static void MyMethod(string parameter1,int parameter2,DateTime parameter3)
    {
        Console.WriteLine("{0} {1} {2}", parameter1,parameter2.ToString(),parameter3.ToString());
    }

    调用如下:

    //Task传入多个参数
    for (int i = 1; i <= 20; i++)
    {              
        new Task(() => { MyMethod("我的线程", i, DateTime.Now); }).Start();
        Thread.Sleep(200);
    }

    运行结果如下:

     

     对于传入多个参数,可以使用无参数委托包装一个多参数的方法来完成。 

    2.4 Task的结果

    要获取Task的结果,在创建Task的时候,就要采用Task<T>来实例化一个Task。

    其中的T就是Task执行完成之后返回结果的类型。

    通过Task实例的Result属性就可以获取结果。

    System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
    Task<int> myTask = new Task<int>(() =>
    {
        int sum = 0;
        for (int i = 0; i < 10000; i++)
            sum += i;
        return sum;
    });
    myTask.Start();           
    Console.WriteLine("结果: {0} 耗时:{1}", myTask.Result,watch.ElapsedMilliseconds);

    运行结果如下:

    使用Factory属性来完成上面的示例:

    //使用Factory属性创建
    System.Diagnostics.Stopwatch watchSecond = System.Diagnostics.Stopwatch.StartNew();
    Task<int> myTaskSecond = Task.Factory.StartNew<int>(() =>
    {
        int sum = 0;
        for (int i = 0; i < 10000; i++)
            sum += i;
        return sum;
    });            
    Console.WriteLine("结果: {0} 耗时:{1}", myTaskSecond.Result, watchSecond.ElapsedMilliseconds);

    运行结果如下:

    多线程除以上的一些基础知识,在处理各种并行任务和多核编程中的使用,小伙伴可以参考专门关于多线程的书籍学习。

    想要完全深入的学习多线程需要慢慢修炼,不断积累。 

    3. 本节要点:

    A.本点简单介绍了线程池ThreadPool的使用;

    B.介绍一使用Task进行多线程创建及Tast的参数传入和返回结果。

    ==============================================================================================

    返回目录

    <如果对你有帮助,记得点一下推荐哦,如有有不明白或错误之处,请多交流>

    <对本系列文章阅读有困难的朋友,请先看《.net 面向对象编程基础》>

    <转载声明:技术需要共享精神,欢迎转载本博客中的文章,但请注明版权及URL>

    .NET 技术交流群:467189533 .NET 程序设计

    ==============================================================================================

  • 相关阅读:
    还能这样偷懒?用Python实现网站自动签到脚本
    普通爬虫 VS 多线程爬虫!Python爬虫运行时间对比
    中文文献阅读方法及笔记模板
    约束
    可迭代对象补充
    练习题及补充
    内置函数的补充/super/异常值处理
    特殊成员
    嵌套
    面向对象知识点总结补充
  • 原文地址:https://www.cnblogs.com/yubinfeng/p/4679063.html
Copyright © 2020-2023  润新知