C# 多线程学习之基础入门

目录
  • 同步方式
  • 异步多线程方式
  • 异步多线程优化
  • 异步回调
  • 异步信号量
  • 异步多线程返回值
  • 异步多线程返回值回调

线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。进程是资源分配的基本单位。所有与该进程有关的资源,都被记录在进程控制块PCB中。以表示该进程拥有这些资源或正在使用它们。本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成异步多线程方式,仅供学习分享使用,如有不足之处,还请指正。

同步方式

业务场景:用户点击一个按钮,然后做一个耗时的业务。同步方式代码如下所示:

private void btnSync_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnSync_Click同步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    for (int i = 0; i < 5; i++)
    {
        string name = string.Format("{0}_{1}", "btnSync_Click", i);
        this.DoSomethingLong(name);
    }
    Console.WriteLine("************btnSync_Click同步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    watch.Stop();
    Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
}

/// <summary>
/// 模拟做一些长时间的工作
/// </summary>
/// <param name="name"></param>
private void DoSomethingLong(string name)
{
    Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
    //CPU计算累加和
    long rest = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        rest += i;
    }
    Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);

}

同步方式输出结果,如下所示:

通过对以上示例进行分析,得出结论如下:

  1. 同步方式按顺序依次执行。
  2. 同步方式业务和UI采用采用同一线程,都是主线程。
  3. 同步方式如果执行操作比较耗时,前端UI会卡住,无法响应用户请求。
  4. 同步方式比较耗时【本示例9.32秒】

异步多线程方式

如何优化同步方式存在的问题呢?答案是由同步方式改为异步异步多线程方式。代码如下所示:

private void btnAsync_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    Action<string> action = new Action<string>(DoSomethingLong);
    for (int i = 0; i < 5; i++)
    {
        string name = string.Format("{0}_{1}", "btnAsync_Click", i);
        action.BeginInvoke(name,null,null);
    }
    Console.WriteLine("************btnAsync_Click异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    watch.Stop();
    Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
}

异步方式出结果,如下所示:

通过对以上示例进行分析,得出结论如下:

  1. 异步方式不是顺序执行,即具有无序性。
  2. 异步方式采用多线程方式,和UI不是同一个线程,所以前端UI不会卡住。
  3. 异步多线程方式执行时间短,响应速度快。

通过观察任务管理器,发现同步方式比较耗时间,异步方式比较耗资源【本例是CPU密集型操作】,属于以资源换性能。同步方式和异步方式的CPU利用率,如下图所示:

异步多线程优化

通过上述例子,发现由于采用异步的原因,线程还未结束,但是排在后面的语句就先执行,所以统计的程序执行总耗时为0秒。为了优化此问题,采用async与await组合方式执行,代码如下所示:

private async void btnAsync2_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync_Click2异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    await DoAsync();
    Console.WriteLine("************btnAsync_Click2异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    watch.Stop();
    Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
}

/// <summary>
/// 异步方法
/// </summary>
/// <returns></returns>
private async Task DoAsync() {
    Action<string> action = new Action<string>(DoSomethingLong);
    List<IAsyncResult> results = new List<IAsyncResult>();
    for (int i = 0; i < 5; i++)
    {
        string name = string.Format("{0}_{1}", "btnAsync_Click", i);
        IAsyncResult result = action.BeginInvoke(name, null, null);
        results.Add(result);
    }
    await Task.Run(()=> {
        while (true)
        {
            for (int i = 0; i < results.Count; i++) {
                var result = results[i];
                if (result.IsCompleted) {
                    results.Remove(result);
                    break;
                }
            }
            if (results.Count < 1) {
                break;
            }
            Thread.Sleep(200);

        }
    });

}

经过优化,执行结果如下所示:

通过异步多线程优化后的执行结果,进行分析后得出的结论如下:

  1. Action的BeginInvoke,会返回IAsyncResult接口,通过接口可以判断是否完成。
  2. 如果有多个Action的多线程调用,可以通过List方式进行。
  3. async与await组合,可以实现异步调用,防止线程阻塞。

通过以上方式,采用异步多线程的方式,共耗时3.26秒,比同步方式的9.32秒,提高了2.85倍,并非线性增加。且每次执行的总耗时会上下浮动,并非固定值。

异步回调

 上述async与await组合,是一种实现异步调用的方式,其实Action本身也具有回调函数【AsyncCallback】,通过回调函数一样可以实现对应功能。具体如下所示:

/// <summary>
/// 异步回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnAsync3_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync_Click3异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    Action action = DoAsync3;
    AsyncCallback asyncCallback = new AsyncCallback((ar) =>
    {
        if (ar.IsCompleted)
        {
            Console.WriteLine("************btnAsync_Click3异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
            watch.Stop();
            Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
        }
    });
    action.BeginInvoke(asyncCallback, null);

}

private void DoAsync3()
{
    Action<string> action = new Action<string>(DoSomethingLong);
    List<IAsyncResult> results = new List<IAsyncResult>();
    for (int i = 0; i < 5; i++)
    {
        string name = string.Format("{0}_{1}", "btnAsync_Click3", i);
        IAsyncResult result = action.BeginInvoke(name, null, null);
        results.Add(result);
    }

    while (true)
    {
        for (int i = 0; i < results.Count; i++)
        {
            var result = results[i];
            if (result.IsCompleted)
            {
                results.Remove(result);
                break;
            }
        }
        if (results.Count < 1)
        {
            break;
        }
        Thread.Sleep(200);

    }
}

异步回调执行示例,如下所示:

通过对异步回调方式执行结果进行分析,结论如下所示:

  1. 通过观察线程ID可以发现,由于对循环计算的功能进行了封装,为一个独立的函数,所以在Action通过BeginInvoke发起时,又是一个新的线程。
  2. 通过async和await在通过Task.Run方式返回时,也会重新生成新的线程。
  3. 通过回调函数,可以保证异步线程的执行顺序。
  4. 通过Thread.Sleep(200)的方式进行等待,会有一定时间范围延迟。

异步信号量

信号量方式是通过BeginInvoke返回值IAsyncResult中的异步等待AsyncWaitHandle触发信号WaitOne,可以实现信号的实时响应,具体代码如下:

private void btnAsync4_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync_Click4异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    Action action = DoAsync3;
    var asyncResult = action.BeginInvoke(null, null);
    //此处中间可以做其他的工作,然后在最后等待线程的完成
    asyncResult.AsyncWaitHandle.WaitOne();
    Console.WriteLine("************btnAsync_Click4异步方法 结束,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    watch.Stop();
    Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
}

信号量示例截图如下所示:

通过对异步信号量方式的测试结果进行分析,得出结论如下:

  1. 信号量方式会造成线程的阻塞,且会造成前端界面卡死。
  2. 信号量方式适用于异步方法和等待完成之间还有其他工作需要处理的情况。
  3. WaitOne可以设置超时时间【最多可等待时间】。

异步多线程返回值

上述示例的委托都是无返回值类型的,那么对于有返回值的函数,如何获取呢?答案就是采用Func。示例如下所示:

private void btnAsync5_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync5_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    string name = string.Format("{0}_{1}", "btnAsync_Click5", 0);
    Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn);
    IAsyncResult asyncResult = func.BeginInvoke(name, null, null);
    //此处中间可以做其他的工作,然后在最后等待线程的完成
    int result = func.EndInvoke(asyncResult);
    Console.WriteLine("************btnAsync5_Click异步方法 结束,线程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId,result);
    watch.Stop();
    Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
}

private int DoSomethingLongAndReturn(string name)
{
    Console.WriteLine("************DoSomethingLong 开始 name= {0} 线程ID= {1} 时间 = {2}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"));
    //CPU计算累加和
    long rest = 0;
    for (int i = 0; i < 1000000000; i++)
    {
        rest += i;
    }
    Console.WriteLine("************DoSomethingLong 结束 name= {0} 线程ID= {1} 时间 = {2} 结果={3}************", name, Thread.CurrentThread.ManagedThreadId, DateTime.Now.ToString("HH:mm:ss.fff"), rest);
    return DateTime.Now.Day;
}

采用Func方式的EndInvoke,可以获取返回值,示例如下:

通过对Func方式的EndInvoke方法的示例进行分析,得出结论如下所示:

  1. 在主线程中调用EndInvoke,会进行阻塞,前端页面卡死。
  2. Func的返回值是泛型类型,可以返回任意类型的值。

异步多线程返回值回调

为了解决以上获取返回值时,前端页面卡死的问题,可以采用回调函数进行解决,如下所示:

private void btnAsync6_Click(object sender, EventArgs e)
{
    Stopwatch watch = Stopwatch.StartNew();
    watch.Start();
    Console.WriteLine("************btnAsync6_Click异步方法 开始,线程ID= {0}************", Thread.CurrentThread.ManagedThreadId);
    string name = string.Format("{0}_{1}", "btnAsync_Click6", 0);
    Func<string, int> func = new Func<string, int>(DoSomethingLongAndReturn);
    AsyncCallback callback = new AsyncCallback((asyncResult) =>
    {
        int result = func.EndInvoke(asyncResult);
        Console.WriteLine("************btnAsync6_Click异步方法 结束,线程ID= {0},返回值={1}************", Thread.CurrentThread.ManagedThreadId, result);
        watch.Stop();
        Console.WriteLine("************总耗时= {0}************", watch.Elapsed.TotalSeconds.ToString("0.00"));
    });
    func.BeginInvoke(name, callback, null);
}

采用回调方式,示例截图如下:

通过对回调方式的示例进行分析,得出结论如下:

  1. 异步回调函数中调用EndInvoke,可以直接返回,不再阻塞。
  2. 异步回调方式,前端UI线程不再卡住。 

以上就是C# 多线程学习之基础入门的详细内容,更多关于C# 多线程的资料请关注我们其它相关文章!

(0)

相关推荐

  • c# 多线程处理多个数据的方法

    概述 多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系统包括对称多处理机.多核心处理器以及芯片级多处理或同时多线程处理器.在一个程序中,这些独立运行的程序片段叫作"线程"(Thread),利用它编程的概念就叫作"多线程处理". 队列(Queue)代表了一个先进先出的对象集合.当您需要对各项进行先进先出的访问时,则使用队列.

  • c# 多线程编程 入门篇

    开始本应该是一篇洋洋洒洒的文字, 不过我还是提倡先做起来, 在尝试中去理解. 先试试这个: procedure TForm1.Button1Click(Sender: TObject);var i: Integer;begin for i := 0 to 500000 do begin Canvas.TextOut(10, 10, IntToStr(i)); end;end; 上面程序运行时, 我们的窗体基本是 "死" 的, 可以在你在程序运行期间拖动窗体试试... Delphi 为我

  • C# 异步多线程入门基础

    目录 进程.线程 1. 进程 2. 线程 分时.分片 同步.异步 异步.多线程 异步多线程效率 多线程无序性 扩展 异步多线程版本 下一篇:C# 异步多线程入门到精通之Thread篇 进程.线程 1. 进程 首先了解,什么是线程? 即一个应用程序运行时,占用资源的综合是一个进程.Windows 任务管理器里面可以看到,里面一个个都是在运行的进程. 2. 线程 线程是执行流的最小单位.线程其实是看不到的,其实也可以,例如 Windows 任务管理器:正在运行 272 个进程,272 个进程运行了

  • C# 多线程编程技术基础知识入门

    什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源.而一个进程又是由多个线程所组成的. 什么是线程? 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针.程序计数器等),但代码区是共享的,即不同的线程可以执行同样的函数. 什么是多线程? 多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务. 多线程是指程序中包含多个执行流,即在一个程序中

  • 深入了解c#多线程编程

    一.使用线程的理由 1.可以使用线程将代码同其他代码隔离,提高应用程序的可靠性. 2.可以使用线程来简化编码. 3.可以使用线程来实现并发执行. 二.基本知识 1.进程与线程:进程作为操作系统执行程序的基本单位,拥有应用程序的资源,进程包含线程,进程的资源被线程共享,线程不拥有资源. 2.前台线程和后台线程:通过Thread类新建线程默认为前台线程.当所有前台线程关闭时,所有的后台线程也会被直接终止,不会抛出异常. 3.挂起(Suspend)和唤醒(Resume):由于线程的执行顺序和程序的执行

  • 深入分析C#中的异步和多线程

    许多开发人员对异步代码和多线程以及它们的工作原理和使用方法都有错误的认识.在这里,你将了解这两个概念之间的区别,并使用c#实现它们. 我:"服务员,这是我第一次来这家餐厅.通常需要4个小时才能拿到食物吗?" 服务员:"哦,是的,先生.这家餐厅的厨房里只有一个厨师." 我:"--只有一个厨师吗?" 服务员:"是的,先生,我们有好几个厨师,但每次只有一个在厨房工作." 我:"所以其他10个穿着厨师服站在厨房里的人--什么

  • C# 多线程学习之基础入门

    目录 同步方式 异步多线程方式 异步多线程优化 异步回调 异步信号量 异步多线程返回值 异步多线程返回值回调 线程(英语:thread)是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务.进程是资源分配的基本单位.所有与该进程有关的资源,都被记录在进程控制块PCB中.以表示该进程拥有这些资源或正在使用它们.本文以一些简单的小例子,简述如何将程序由同步方式,一步一步演变成

  • 轻松学习C#的基础入门

    C#是一种简单的,安全的,稳定的,由C和C++衍生出来的面向对象的编程语言,它在继承C和C++强大功能的同时,去掉了它们的一些复杂性,综合了Visual Studio简单的可视化操作和C++高效的运行效率,以其强大的操作能力,独特的语法风格,创新的语言特性,便捷的面向组件编程的支持,使其成为.NET开发的首选语言,并成为ECMA和ISO的标准规范.        C#和Java有着惊人的相似,主要包括诸如单一继承,接口,与Java几乎同样的语法和编译成中间代码在运行的过程.但是C#和Java有着

  • Java commons io包实现多线程同步图片下载入门教程

    目的: 实现多线程同时下载网络图片,入门级. 多线程入门 commons io: 是针对开发IO流功能的工具类库,其中包含了许多可调用的函数. 1.commons io 可直接百度,进入官网直接下载即可 Linux下载tar.gz,window下载.zip. 2.解压commons io ,复制下面的java文件,后在项目中,新建package,我的名为lib,如下,将复制的java文件粘贴到package中,并鼠标右击此文件,点击add as a library即可. 3.代码如下:多线程基础

  • JavaScript_object基础入门(必看篇)

    之前写Java时老是有点蒙,大部分都是用jQuery,但原理还不是很清楚,最近一段时间在系统的学习JavaScript,有什么问题或错误请指出,多谢..................... Object所有类的基础类 var obj = new Object(); var obj = {}; //实例化对象 给对象设置属性分为两种: 1.使用直接量的方式:对象.属性/方法,这种方式直观.易懂 obj.name = '张三'; obj.age = 20; obj.sex = '男'; obj.s

  • Java 多线程学习详细总结

    目录(?)[-] 一扩展javalangThread类 二实现javalangRunnable接口 三Thread和Runnable的区别 四线程状态转换 五线程调度 六常用函数说明 使用方式 为什么要用join方法 七常见线程名词解释 八线程同步 九线程数据传递 本文主要讲了java中多线程的使用方法.线程同步.线程数据传递.线程状态及相应的一些线程函数用法.概述等. 首先讲一下进程和线程的区别: 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1

  • spring boot 学习笔记(入门篇)

    简介: Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置.用我的话来理解,就是spring boot其实不是什么新的框架,它默认配置了很多框架的使用方式,就像maven整合了所有的jar包,spring boot整合了所有的框架(不知道这样比喻是否合适). 优点: 其实就是简单.快速.方便!平时如果我们需要搭建一个spring web项目的时候需要怎么

  • React Native基础入门之初步使用Flexbox布局

    前言 在上篇中,笔者分享了部分安装并调试React Native应用过程里的一点经验,如果还没有看过的同学请点击<React Native基础&入门教程:调试React Native应用的一小步>. 在本篇里,让我们一起来了解一下,什么是Flexbox布局,以及如何使用. 一.长度的单位 在开始任何布局之前,让我们来首先需要知道,在写React Native组件样式时,长度的不带单位的,它表示"与设备像素密度无关的逻辑像素点". 这个怎么理解呢? 我们知道,屏幕上一

  • Android开发之InetAddress基础入门简介与源码实例

    最近在学习soket编程中,看到有需要获取到IP地址之类的需求,所以就去看了下如何获取到主机名的IP地址. 其实就是需要用到一个类InetAddress.他是在java.net包下面. InetAddress类的对象用于IP地址和域名,该类提供以下方法: getByName(String s):获得一个InetAddress 类的对象,该对象中含有主机的IP地址和域名,该对象用如下格式表示它包含的信息:www.sina.com.cn/202.108.37.40: String getHostNa

  • Android 基础入门教程——开发环境搭建

    现在主流的Android开发环境有: Eclipse + ADT + SDK Android Studio + SDK IntelliJ IDEA + SDK 现在国内大部分开发人员还是使用的Eclipse,而谷歌宣布不再更新ADT后,并且官网也去掉了集成Android开发环境的Eclipse下载链接,各种现象都表示开发者最后都终将过渡到Android Studio,当然这段过渡时间会很长,但如果你是刚学Android的话建议直接冲Android Studio着手:而且很多优秀的开源项目都是基于

随机推荐