C#学习(十三)——多线程与异步

这篇具有很好参考价值的文章主要介绍了C#学习(十三)——多线程与异步。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、什么是线程

程序执行的最小单元
一次页面的渲染、一次点击事件的触发、一次数据库的访问、一次登录操作都可以看作是一个一个的进程

在一个进程中同时启用多个线程并行操作,就叫做多线程
由CPU来自动处理
线程有运行、阻塞、就绪三态

代码示例:

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(() =>
        {
            Print1();
        });

        thread.Start();

        for(int i = 0; i < 1000; i++)
        {
            Console.Write(0);
        }

        Console.Read();
    }
    
    static void Print1()
    {
        for (int i = 0;i < 1000; i++)
        {
            Console.Write(1);
        }
    }

}

运行结果为
C#学习(十三)——多线程与异步,c#,学习,开发语言,异步,多线程
可以看到,在结果中,0和1的输出是交织在一起的,原因为两个线程交替着被运行,不断反复直到结束。
另外一个常用操作为Sleep();让时间暂停,使得线程进入静默状态。

二、前台线程、后台线程与线程池托管

代码举例:

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(PrintHello);
        thread.Start();
        Console.WriteLine("退出主程序");
    }

    private static void PrintHello(object? obj)
    {
        while (true)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Hello from PrintHello!");
        }
    }
}

运行会发现,即使主线程运行结束了,子线程依旧在持续运行;持续运行的子线程就称为前台线程
一般来说,只有等待前台线程运行完毕后,程序才可以进行关闭。
与之对应的是 后台线程,可以通过thread.IsBackground = true;//切换为后台线程将前台 线程切换到后台线程,这样再次运行会发现,当主线程结束后,后台线程就会被强制结束。

一般来说,前台线程用于需要时间比较长的等待业务,比如监听客户端请求,而后台线程适用于时较短的业务比如执行客户端发来的请求,后台进程不会影响程序的终止。
托管在线程池中的线程全部为后台线程
所有使用new Thread创建的线程默认均为前台线程

三、线程池

示例代码

for(int i = 0;i < 100; i++)
{
    ThreadPool.QueueUserWorkItem((o) =>
    {
        Console.WriteLine($"循环次数{i} 线程id {Thread.CurrentThread.ManagedThreadId}");
    });
}

C#学习(十三)——多线程与异步,c#,学习,开发语言,异步,多线程
可以看到执行结果出现了id重复的状况,原因就是线程池会重复使用已经完成的线程,极大节约硬件资源。
另外,可以看到,for循环有100次,但是从输出结果来看,只执行了十几次,原因为线程池创建的线程均为后台线程,只要主程序退出,线程池的后台线程就会被停止,而主程序main执行的时间很短,因此线程池内线程没有来得及执行就被停止了。

对于重要的并发量小的线程,需要手动创建管理,对于并发量大而又不太重要的线程,最好托管到线程池中。

四、结束线程与CancellationToken

不管程序有多少个进程,进程内部的资源都是被共享的。所以C#对进程的取消代码作了更高层次的抽象,把进程的取消过程封装成为了Token的形式,也就是CancellationToken(取消令牌)。不仅可以使用在多线程中,还可以用于异步操作。

class Program
{
    static void Main(string[] args)
    {
        CancellationTokenSource cts = new CancellationTokenSource();

        Thread thread = new Thread(() => { PrintHello(cts.Token); });
        thread.Start();

        //下载文件
        Thread.Sleep(5000);

        //关闭子进程
        //cts.Cancel();
        cts.CancelAfter(3000);//在下载完成后3s失效


        Console.WriteLine("退出主程序");
    }

    private static void PrintHello(CancellationToken tokenSource)
    {
        while (!tokenSource.IsCancellationRequested)
        {
            Thread.Sleep(1000);
            Console.WriteLine("Hello from PrintHello!");
        }
    }
}

五、Join与IsAlive

对于子线程执行时间不确定的情况,需要使用Join的方法,加入至主程序执行中,或者使用IsAlive方法进行判断

class Program
{
    static void Main(string[] args)
    {
        Thread thread = new Thread(() => { PrintHello(); });
        thread.Start();

        //方法一
        //thread.Join();

        //方法二
        while(thread.IsAlive)
        {
            Console.WriteLine("子线程仍在工作");
            Thread.Sleep(100);
        }

        Console.WriteLine("退出主程序");
    }

    private static void PrintHello()
    {
        int i = 0;
        while (i++ < 10)
        {
            Thread.Sleep(new Random().Next(100, 1000));
            Console.WriteLine("Hello from PrintHello!");
        }
    }
}

六、资源竞争与线程锁lock

使用线程可以并发的在CPU的核心中执行任务,最大化CPU的利用率,但是并发执行任务也可能产生各种各样的资源竞争问题。

举例:

    private static void AddText()
    {
        File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}");
        Thread.Sleep(100);
        File.AppendAllText((@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}");
    }

当两个线程同时需要使用同一个文件资源时,产生资源竞争,导致系统崩溃。
因此必须保证同一时刻只能有一个线程访问资源,避免出现资源恶性竞争。
使用线程锁就可以解决

class Program
{
    static object lockedObj = new object();
    static void Main(string[] args)
    {
        for(int i = 0; i < 10; i++)
        {
            var t = new Thread(AddText); 
            t.Start();
        }

        Console.WriteLine("退出主程序");
    }

    private static void AddText()
    {
        lock(lockedObj)
        {
            File.AppendAllText(@"D:\test.txt", $"开始{Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(100);
            File.AppendAllText(@"D:\test.txt", $"结束{Thread.CurrentThread.ManagedThreadId}");
        }           
    }
}

七、异步

在之前项目中,我们实现的所有操作都是同步进行的,然而当有同时10000个请求发生时,会使得用户有很长的等待,服务器会等待数据库的响应,完成后反馈至用户。
而异步操作要实现,不要等待数据库,继续执行下一个请求,当数据返回数据以后,再回头继续处理上一个请求。
然而对于更高级别的数量请求,仅仅依靠异步也是不够的,因此需要:
异步服务+每个机器多开进程+多个机器组合实现;
K8s, Kubernetess容器化分布式部署;
.NET Core对容器化非常非常友好、支持度极高

八、异步编程Task

我们使用异步处理并行,使用多线程处理并发。

异步逻辑是要基于方法没有依赖关系的,例如

 class Program
 {
     static void Main(string[] args)
     {

         Calculate();
         Console.Read();

     }

     static void Calculate()
     {
         //Task->异步,Thread->线程
         Task.Run(() =>
         {
             Calculate1();
         });
         Task.Run(() =>
         {
             Calculate2();
         });
         Task.Run(() =>
         {
             Calculate3();
         });
     }

     static int Calculate1()
     {
         var result = 3;
         Console.WriteLine($"Calculate1: {result}");
         Task.Delay(2000);
         return result;
     }

     static int Calculate2()
     {
         var result = 4;
         Console.WriteLine($"Calculate2: {result}");
         Task.Delay(3000);
         return result;
     }

     static int Calculate3()
     {
         var result = 5;
         Console.WriteLine($"Calculate3: {result}");
         Task.Delay(1000);
         return result;
     }
 }

但如果是具有依赖关系的,例如,Caculate2()需要Caculate1()的结果,Caculate3需要Caculate1()和Caculate2()的结果,那么就需要做如下调整

class Program
{
    static void Main(string[] args)
    {

        Calculate();
        Console.Read();

    }

    static void Calculate()
    {
        //Task->异步,Thread->线程
        var task1 = Task.Run(() =>
        {
            return Calculate1();
        });

        var awaiter1 = task1.GetAwaiter();//获得异步等待对象
        awaiter1.OnCompleted(() =>
        {
            var result1 = awaiter1.GetResult();//获得异步逻辑的最终计算结果
            var task2 = Task.Run(() =>
            {
                return Calculate2(result1);
            });
            var awaiter2 = task2.GetAwaiter();
            awaiter2.OnCompleted(() =>
            {
                var result2 = awaiter2.GetResult();
                var result = Calculate3(result1, result2);
                Console.WriteLine(result);
            });
        });          
    }

    static int Calculate1()
    {
        var result = 3;
        Console.WriteLine($"Calculate1: {result}");
        Task.Delay(2000);
        return result;
    }

    static int Calculate2(int a)
    {
        var result = a * 2;
        Console.WriteLine($"Calculate2: {result}");
        Task.Delay(3000);
        return result;
    }

    static int Calculate3(int a, int b)
    {
        var result = a + b;
        Console.WriteLine($"Calculate3: {result}");
        Task.Delay(1000);
        return result;
    }
}

九、C#的异步 async/await

可以看到,上面的异步操作代码非常复杂繁琐,接下来使用async/await化解上面操作

同步方法
指程序调用某个方法,需要等待执行完成以后才进行下一步操作
异步方法
指程序调用某个方法的时候,不做任何等待,在处理完成之前就返回该方法,继续执行接下来的操作,即函数在执行完成前就可以先返回调用方,然后继续执行接下来的逻辑完成任务的函数

举例:

public async Task<int> DoSomethingAsync()
{
    //创建一个计算1万毫秒的任务
    Task<int> longRunningTask = LongRunningTaskAsync();
    //使用await执行这个任务
    int result = await longRunningTask;
    return result;
}
//假装计算1w毫秒,输出为1
private async Task<int> LongRunningTaskAsync()
{
    await Task.Delay(10000);//延迟10s
    return 1;
}

1.需要使用async关键词
2.返回类型为:voidTaskTask<T>IAsyncEnumerable<T>
3.命名规范:Async结尾
4.需要有await表达式
5.要有返回值
6.async函数只能被async函数调用

共有三个部分:
第一部分异步调用:Task<int> longRunningTask = LongRunningTaskAsync();
第二部分执行异步:int result = await longRunningTask;
第三部分异步方法:private async Task<int> LongRunningTaskAsync(){}

注:

  • [ 在函数声明中,async关键字要放到返回类型之前 ]
  • [ async函数本身不创建异步操作,只有在调用await的时候才会进行异步操作 ]

下面对之前的异步代码进行优化:

class Program
{
    static void Main(string[] args)
    {
        Calculate();
        Console.Read();
    }

    static async void Calculate()
    {
        var result1 = await Calculate1Async();

        var result2 = await Calculate2Async(result1);

        var result = await Calculate3Async(result1, result2);

        Console.WriteLine(result);          
    }

    static async Task<int> Calculate1Async()
    {
        var result = 3;
        Console.WriteLine($"Calculate1: {result}");
        await Task.Delay(2000);
        return result;
    }

    static async Task<int> Calculate2Async(int a)
    {
        var result = a * 2;
        Console.WriteLine($"Calculate2: {result}");
        await Task.Delay(3000);
        return result;
    }

    static async Task<int> Calculate3Async(int a, int b)
    {
        var result = a + b;
        Console.WriteLine($"Calculate3: {result}");
        await Task.Delay(1000);
        return result;
    }
}

十、Task VS. Thread

异步不是多线程!!!
异步用来处理并行,多线程用于处理并发

class Program
{
    static void Main(string[] args)
    {
        TaskTest();
        ThreadTest();
        
        Console.Read();
    }

    static void TaskTest()
    {
        var sw = new Stopwatch();
        sw.Start();

        for(int i = 0; i < 100; i++)
        {
            Task.Factory.StartNew(() => { });
        }

        sw.Stop();
        Console.WriteLine($"Task {sw.ElapsedMilliseconds}");
    }

    static void ThreadTest()
    {
        var sw = new Stopwatch();
        sw.Start();

        for (int i = 0; i < 100; i++)
        {
            new Thread(() => { }).Start();
        }

        sw.Stop();
        Console.WriteLine($"Thread {sw.ElapsedMilliseconds}");
    }
}

执行结果为
C#学习(十三)——多线程与异步,c#,学习,开发语言,异步,多线程
可以看到Task的执行速度要远高于Thread!
异步并不会创建线程,只是通过主线程来执行,同时开出一条分路来执行其他任务,非同步分别执行。但是在最后会创建一个非常轻量级的Worker Thread,用于通知主程序异步结束,也称为回调Call Back.文章来源地址https://www.toymoban.com/news/detail-826698.html

到了这里,关于C#学习(十三)——多线程与异步的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 鸿蒙开发笔记(十三): 线程模型,线程间通信,Emitter,Workder

    HarmonyOS应用中每个进程都会有一个主线程,主线程有如下职责: 执行UI绘制; 管理主线程的ArkTS引擎实例,使多个UIAbility组件能够运行在其之上; 管理其他线程(例如Worker线程)的ArkTS引擎实例,例如启动和终止其他线程; 分发交互事件; 处理应用代码的回调,包括事件处

    2024年01月23日
    浏览(28)
  • 【已解决】C语言实现多线程的同步与异步

    说真的写了这篇博文时,才知道c语言本身不支持多线程,而是一些windowsapi让c语言拥有多线程的能力,那下面内容就以打开对话框为例,展现如何实现多线程的同步与异步。 想要实现c语言打开多个对话框的多线程同步与异步 代码效果 对代码的查阅会发现,关键在于定义多线

    2024年02月02日
    浏览(29)
  • c# 学习笔记 - 异步编程

    1.1 简单介绍   异步编程官方参考文档:异步编程     1.2 async/await 使用   细节注意 async 用来修饰方法,表示这个方法可以成为一个异步方法,但是如果内部没有搭配使用 await 的话其作用还是等效于一个同步方法 await 必须用于在 async 修饰的异步方法内使用,

    2024年01月18日
    浏览(40)
  • C#基础学习--异步编程

    目录 什么是异步 async/await 特性的结构  什么是异步方法  异步方法的控制流 await 表达式 启动程序时,系统会在内存中创建一个新的 进程 。进程是构成运行程序的资源的集合。进程是构成运行程序的资源的集合。这些资源包括虚地址空间,文件句柄和许多其他程序运行所需

    2023年04月25日
    浏览(34)
  • C#多线程开发详解

    多线程允许程序同时执行多个任务,从而有效利用多核处理器,加快程序的执行速度。特别是在需要处理大量计算、I/O 操作或并行任务的应用中,多线程可以显著提高性能。 多线程使应用能够同时处理多个用户请求或事件,提高了应用的响应性。例如,多线程可以保持用户

    2024年02月13日
    浏览(25)
  • C#多线程学习(二) 如何操纵一个线程

    线程学习第一篇: C#多线程学习(一) 多线程的相关概念 下面我们就动手来创建一个线程,使用Thread类创建线程时,只需提供线程入口即可。(线程入口使程序知道该让这个线程干什么事) 在C#中,线程入口是通过ThreadStart代理(delegate)来提供的,你可以把ThreadStart理解为一个

    2023年04月19日
    浏览(35)
  • C#多线程学习(一) 多线程的相关概念

    什么是进程? 当一个程序开始运行时,它就是一个进程,进程包括运行中的程序和程序所使用到的内存和系统资源。 而一个进程又是由多个线程所组成的。 什么是线程? 线程是程序中的一个执行流,每个线程都有自己的专有寄存器(栈指针、程序计数器等), 但代码区是共享

    2023年04月13日
    浏览(30)
  • 《Go 语言第一课》课程学习笔记(十三)

    Go 语言从设计伊始,就不支持经典的面向对象语法元素,比如类、对象、继承,等等,但 Go 语言仍保留了名为“方法(method)”的语法元素。当然,Go 语言中的方法和面向对象中的方法并不是一样的。Go 引入方法这一元素,并不是要支持面向对象编程范式,而是 Go 践行组合

    2024年02月10日
    浏览(30)
  • SpringBoot(十三)异步任务

    目录 异步任务 1.1 什么叫异步 1、Java线程处理 2、SpringBoot异步任务 2.1 使用注解@EnableAsync开启异步任务支持 2.2、使用@Async注解标记要进行异步执行的方法 2.3、controller测试 3、异步任务相关限制 4、自定义 Executor(自定义线程池) 4.1、应用层级: 4.2、方法层级: 有时候,前端

    2024年02月04日
    浏览(28)
  • unity的C#学习——多线程编程(线程的生命周期、创建与管理)与线程相关类

    多线程编程是 C# 一个比较难且涵盖面比较广的知识点,本文整理仓促而且属于笔记向博客,有些地方必然还存在不严谨和错误,本人会在日后的使用过程中不断完善。如果发现问题或有改进意见可以在评论区提出,我会及时修改。 线程是程序的执行流程,也被称为 轻量级进

    2024年02月12日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包