C# 异步编程与多线程简析:Thread、ThreadPool、Task

439 阅读5分钟

前言

本文旨在帮助读者理解C#中的异步编程与多线程概念,探讨如何在C#中实现多线程,并对比分析不同实现方法的特点及其适用场景。

全文分为两大部分,第一部分介绍异步编程模式async/await及其与多线程的区别,第二部分则聚焦于C#中实现多线程的三种主要方法:ThreadThreadPoolTask

第一部分:异步编程模式 async/await 与多线程的区别

1.1、异步编程模式简介

异步编程模式是一种编程接口设计,主要用于处理并发流程需求。async/await是C#中实现异步编程的主要方式之一。这种模式允许开发者编写非阻塞代码,提高程序的响应性和效率。

1.2、async/await使用示例

static void Main(string[] args)  
{  
    _ = Async1();  
    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  

static async Task Async1()  
{  
    Console.WriteLine("异步开始");  
    var r = await Async2();  
    var x = await Async3(r);  
    Console.WriteLine("结果是 {0}", r + x);  
}  

static async Task<intAsync2()  
{  
    await Task.Delay(1000); // 异步延迟方法  
    return 100;  
}  

static async Task<intAsync3(int x)  
{  
    await Task.Delay(1000);  
    return x % 7;  
}  

执行结果

通过上述代码可以看到,async关键字修饰的方法为异步方法,而await关键字用于标记异步操作的开始点。只有当两者配合使用时,方法才会真正实现异步执行。

1.3、async/await 的特点

异步执行:在遇到await关键字时,方法会暂停执行,不会阻塞当前线程,而是允许其他任务继续运行。

代码可读性:虽然async/await提高了代码的并发能力,但在某些情况下,可能会降低代码的可读性。

返回值类型async方法可以返回voidTaskTask<T>

第二部分:C# 多线程实现方法

2.1、Thread类

Thread类是最基本的多线程实现方式,适用于需要长时间运行的线程。

2.1.1 Thread 使用示例
static void Main(string[] args)  
{  
    Thread thread = new Thread(Fun1);  
    // Thread thread = new Thread(() => Fun1(0)); // 多线程调用时有参数传递的写法  
    Console.WriteLine("异步开始");  
    // thread.IsBackground = true; // 设置为后台线程  
    thread.Start();  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  

static void Fun1()  
{  
    var r = Fun2();  
    var x = Fun3(r);  
    Console.WriteLine("结果是 {0}", r + x);  
}  

static int Fun2()  
{  
    Thread.Sleep(1000);  
    return 100;  
}  

static int Fun3(int x)  
{  
    Thread.Sleep(1000);  
    return x % 7;  
}  

执行结果:

2.1.2 Thread 的特点

内存消耗:每次创建新的Thread对象都会消耗一定的内存(约1MB)。

线程类型:默认为前台线程,可以通过IsBackground属性设置为后台线程。

参数传递:可以通过Start方法传递参数,但参数类型必须为object

2.2、ThreadPool线程池

ThreadPool适用于需要频繁创建和销毁线程且每个线程运行时间较短的场景。

2.2.1、ThreadPool使用示例
static void Main(string[] args)  
{  
    Console.WriteLine("主线程执行!");  

    ThreadPool.SetMinThreads(11); // 设置最小线程数  
    ThreadPool.SetMaxThreads(55); // 设置最大线程数  

    for (int i = 1; i <= 10; i++)  
    {  
        ThreadPool.QueueUserWorkItem(new WaitCallback(testFun), i);  
    }  

    Console.WriteLine("主线程结束!");  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  

public static void testFun(object obj)  
{  
    Console.WriteLine(string.Format("{0}:第{1}个线程", DateTime.Now.ToString(), obj.ToString()));  
    Thread.Sleep(5000);  
}  
2.2.2、ThreadPool的特点

线程池特性:线程池是一个静态类,通过QueueUserWorkItem方法将工作项加入线程池。

性能优势:避免了频繁创建和销毁线程的开销,提高了程序性能。

线程管理:无法直接控制线程的开始、挂起和终止。

2.3、Task类

Task类是C#中推荐的多线程实现方式,适用于大多数场景。

2.3.1、Task使用示例
static void Main(string[] args)  
{  
    Console.WriteLine("主线程执行!");  

    // 方法一  
    Task t1 = new Task(() =>  
    {  
        Console.WriteLine("方法1的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法1的任务工作完成……");  
    });  
    t1.Start();  

    // 方法二  
    Task.Run(() =>  
    {  
        Console.WriteLine("方法2的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法2的任务工作完成……");  
    });  

    // 方法三  
    var t3 = Task.Factory.StartNew(() =>  
    {  
        Console.WriteLine("方法3的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法3的任务工作完成……");  
    });  

    Console.WriteLine("主线程结束!");  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  
2.3.2 Task 的特点

灵活性:提供了丰富的API,支持多种管理和控制方法。

性能优化:使用本地队列减少线程之间的资源竞争。

返回值:可以通过Task<T>获取任务的返回值。

2.4、Task的高级用法

2.4.1、带返回值的 Task
static void Main(string[] args)  
{  
    Console.WriteLine("主线程执行!");  

    Task<int> task = CreateTask("Task 1");  
    task.Start();  
    int result = task.Result;  
    Console.WriteLine("Task 1 Result is: {0}", result);  
    Console.WriteLine("主线程结束!");  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  

static Task<intCreateTask(string name)  
{  
    return new Task<int>(() => TaskMethod(name));  
}  

static int TaskMethod(string name)  
{  
    Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",  
        name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);  
    Thread.Sleep(TimeSpan.FromSeconds(2));  
    return 42;  
}  
2.4.2、ContinueWith方法
static void Main(string[] args)  
{  
    Console.WriteLine("主线程执行!");  

    Task t1 = new Task(() =>  
    {  
        Console.WriteLine("方法1的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法1的任务工作完成……");  
    });  
    t1.Start();  

    t1.ContinueWith(t =>  
    {  
        Console.WriteLine("方法1的任务工作完成了!");  
    });  

    Console.WriteLine("主线程结束!");  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  
2.4.3、Task.WaitAll方法
static void Main(string[] args)  
{  
    Console.WriteLine("主线程执行!");  

    Task t1 = new Task(() =>  
    {  
        Console.WriteLine("方法1的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法1的任务工作完成……");  
    });  
    t1.Start();  

    Task t2 = new Task(() =>  
    {  
        Console.WriteLine("方法2的任务开始工作……");  
        Thread.Sleep(5000);  
        Console.WriteLine("方法2的任务工作完成……");  
    });  
    t2.Start();  

    Task.WaitAll(t1, t2);  

    Console.WriteLine("主线程结束!");  

    Console.WriteLine("...............按任意键退出");  
    Console.ReadKey();  
}  

总结

多线程是实现异步的一种方法,C#中常用的多线程实现方式包括ThreadThreadPoolTask。具体选择哪种方法取决于应用场景:

长时间运行的线程:推荐使用Thread

频繁创建和销毁线程且运行时间较短:推荐使用ThreadPool

其他一般场景:推荐使用Task,因其灵活性和性能优势。

希望本文能帮助读者更好地理解和应用C#中的异步编程和多线程技术。

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:hello黄先森

出处:cnblogs.com/hellohxs/p/14358776.html

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!