C# Task和异步方法

ThreadPool中有若干数量的线程。当有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。当线程池中所有的线程都被占用,又有新任务要处理时,线程池会新建一个线程来处理该任务。如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。ThreadPool相对于Thread来说可以减少线程的创建,有效减小系统开销。但是ThreadPool不能控制线程的执行顺序,也不能获取线程池内线程取消/异常/完成的通知,即不能有效监控和控制线程池中的线程。因此NET4.0在ThreadPool的基础上推出了Task。Task拥有线程池的优点,同时也解决了使用线程池不易控制的弊端。

1.无返回值的Task的创建和执行

using System;
using System.Threading.Tasks;
using System.Threading;

namespace TaskDemo
{
    class Program
    {
        static void Main(string[] args)
        {
            // 实例化一个Task,通过Start方法启动
            Task task = new Task(
                () =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine($"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                }
                );

            task.Start();

            // Task.Factory.StartNew(Action action)创建和启动一个Task
            Task task2 = Task.Factory.StartNew(
                () =>
                {
                    Thread.Sleep(500);
                    Console.WriteLine($"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                });
            
            // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task
            Task task3 = Task.Run(
                () =>
                {
                    Thread.Sleep(200);
                    Console.WriteLine($"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}");
                });

            Console.WriteLine("执行主线程");
            Console.Read();
        }
    }
}

运行结果:

 2.用Task.Result获取返回值的Task的创建和执行

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

            // 有返回值的启动task
            Task<string> task = new Task<string>(
                () =>
                {
                    Thread.Sleep(1000);
                    return $"NEW实例化一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
                }
                );

            task.Start();

            // Task.Factory.StartNew(Action action)创建和启动一个Task

            Task<string> task2 = Task.Factory.StartNew(
                () =>
                {
                    Thread.Sleep(3000);
                    return $"Task.Factory.StartNew方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
                });

            // Task.Run(Action action)将任务放在线程池队列,返回并启动一个Task

            Task<string> task3 = Task.Run(
                () =>
                {
                    Thread.Sleep(2000);
                    return $"Task.Run方式创建一个task,线程ID为{Thread.CurrentThread.ManagedThreadId}";
                });

            Console.WriteLine("执行主线程");
            Console.WriteLine(task.Result);
            Console.WriteLine(task2.Result);
            Console.WriteLine(task3.Result);
            Console.Read();
        }
    }
}

运行结果:

可见Task.Result获取返回值时会阻塞线程。本例中,必须等到task2执行完成,获取到返回值后,才能继续执行task3。但是上面两个例子中的Task的执行都是异步的,不会阻塞主线程。

3.同步执行Task,会阻塞主线程

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

            Task task = new Task(
                () =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("执行Task结束");
                }
                );           

            // 同步执行,会阻碍主线程
            task.RunSynchronously();
            Console.WriteLine("执行主线程");
            Console.Read();
        }
    }
}

 运行结果:

 4.Task的阻塞方法(Wait/WaitAll/WaitAny)

使用Task Wait/WaitAll/WaitAny方法,实现阻塞线程

  • task.Wait()表示等待task执行完毕,类似于thread.Join()
  • task.WaitAll(Task[] tasks)表示只有所有的task都执行完毕再解除阻塞
  • task.WaitAny(Task[] tasks)表示只要有一个task执行完毕就解除阻塞
Task task1 = new Task(
                () =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("线程1执行完毕");
                });
            task1.Start();

            Task task2 = new Task(
                () =>
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("线程2执行完毕");
                });
            task2.Start();

            // 阻塞主线程。task1和task2都执行完毕再执行主线程
            //task1.Wait();
            //task2.Wait();
            Task.WaitAll(new Task[] { task1, task2 });
            Console.WriteLine("主线程执行完毕");
            Console.Read();

运行结果:

 使用task1.Wait(); task2.Wait()可以达到同样的目的。如果把WaitAll改成WaitAny,则运行结果如下所示:

 

5.Task的延续操作(WhenAny/WhenAll/ContinueWith)

Wait/WaitAll/WaitAny方法返回值都是void,这些方法只是单纯的实现阻塞线程。使用WhenAny/WhenAll/ContinueWith方法可以让task执行完毕后,继续执行后续操作,这些方法执行完成返回一个task实例。

task.WhenAll(Task[] tasks)表示所有的task都执行完毕后再去执行后续的操作

task.WhenAny(Task[] tasks)表示任一task执行完毕后就开始执行后续操作

Task task1 = new Task(
                () =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("线程1执行完毕");
                });
            task1.Start();

            Task task2 = new Task(
                () =>
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("线程2执行完毕");
                });
            task2.Start();

            Task.WhenAll(new Task[] { task1, task2 }).ContinueWith(
                (t) =>
                {
                    Thread.Sleep(1000);
                    Console.WriteLine("执行后续操作完毕");
                });

            Console.WriteLine("主线程执行完毕");
            Console.Read();

运行结果:

 WhenAll/WhenAny方法并不会阻塞主线程。也可以使用Task.Factory.ContinueWhenAll来实现

Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("执行后续操作完毕");
            });

6.Task的任务取消(CancellationTokenSource)

使用专门类CancellationTokenSource来取消任务执行。

CancellationTokenSource source = new CancellationTokenSource();
            int index = 0;
            Task task1 = new Task(
                () =>
                {
                    while (!source.IsCancellationRequested)
                    {
                        Thread.Sleep(1000);
                        Console.WriteLine($"第{++index}次执行,线程运行中...");
                    }

                });
            task1.Start();
            Console.WriteLine("主线程开始执行");
            Thread.Sleep(5000);
            source.Cancel();
            Console.WriteLine("主线程执行完毕");
            Console.Read();

运行结果:

 还可以使用source.CancelAfter(5000)实现5s后自动取消任务,即Thread.Sleep(5000); source.Cancel();这两条代码由source.CancelAfter(5000)取代。运行结果:

 注意这两次运行结果中,“主线程执行完毕”的区别。也可以通过source.Token.Register(Action action)注册取消任务触发的回调函数。

CancellationTokenSource source = new CancellationTokenSource();
            source.Token.Register(
                () =>
                {
                    Console.WriteLine("任务被取消后执行的操作");
                });
            int index = 0;
            Task task1 = new Task(
                () =>
                {
                    Console.WriteLine($"task1的线程ID是{Thread.CurrentThread.ManagedThreadId}");
                    while (!source.IsCancellationRequested)
                    {
                        Thread.Sleep(1000);
                        Console.WriteLine($"第{++index}次执行,线程运行中...");
                    }

                });
            task1.Start();
            Console.WriteLine($"主线程开始执行,主线程的ID是{Thread.CurrentThread.ManagedThreadId}");
            source.CancelAfter(5000);
            Console.WriteLine("主线程执行完毕");
            
            Console.Read();

运行结果:

7.异步方法(async/await)

async static Task<string>GetContentAsync(string fileName)
        {
            Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"开始读取文件:{DateTime.Now}");
            Thread.Sleep(1000);
            using(StreamReader sr = new StreamReader(fileName))
            {
                string program = await sr.ReadToEndAsync();
                Console.WriteLine($"读取文件结束:{DateTime.Now}");
                return program;
            }
        }

        // 同步读取文件内容
        static string GetContent(string fileName)
        {
            using (StreamReader sr = new StreamReader(fileName))
            {
                string program = sr.ReadToEnd();
                return program;
            }
        }
        static void Main(string[] args)
        {
            string path = @"D:\Demos\TaskDemo\postdata.txt";
            Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"主程序执行开始:{DateTime.Now}");
            string content = GetContentAsync(path).Result;
            Console.WriteLine($"主程序输入结果:{content}");
            Console.WriteLine($"主程序执行结束:{DateTime.Now}");
            Console.Read();
        }

 运行结果:

 主程序等待GetContentAsync方法执行完毕后,获取到返回值后才继续执行。这说明,如果调用方法要从调用中获取一个T类型的值,异步方法的返回类型必须是Task<T>,而且调用会获取到返回值后才会继续执行下去。如果仅仅是调用一下异步方法,不和异步方法做其他交互,则将异步方法签名返回值为void,这种调用形式也被称为“调用并忘记”。

async static void GetContentAsync(string fileName)
        {
            Console.WriteLine($"当前线程ID是{Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"开始读取文件:{DateTime.Now}");
            Thread.Sleep(1000);
            using(StreamReader sr = new StreamReader(fileName))
            {
                string program = await sr.ReadToEndAsync();
                Console.WriteLine($"读取文件结束:{DateTime.Now}");
            }
        }

static void Main(string[] args)
        {
            string path = @"D:\Demos\TaskDemo\postdata.txt";
            Console.WriteLine($"主线程ID是{Thread.CurrentThread.ManagedThreadId}");
            Console.WriteLine($"主程序执行开始:{DateTime.Now}");
            GetContentAsync(path);
            Console.WriteLine($"主程序执行结束:{DateTime.Now}");
            Console.Read();
}

运行结果:

 

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C# 中,可以使用 `Task` 类来创建异步任务。具体实现方式如下: 1. 声明一个异步方法,例如 `DoAsyncTask`,并在方法体中编写你要执行的异步操作。注意,异步方法的返回类型应该是 `Task` 或 `Task<T>`。 ```csharp async Task DoAsyncTask() { // 异步操作代码 } ``` 2. 调用异步方法时,使用 `await` 关键字等待异步方法执行完毕。 ```csharp await DoAsyncTask(); ``` 3. 在异步方法中,可以使用 `Task.Run` 方法来以异步方式执行一个方法。 ```csharp async Task DoAsyncTask() { await Task.Run(() => { // 要异步执行的方法 }); } ``` 完整的代码示例如下: ```csharp using System; using System.Threading.Tasks; class Program { static async Task Main(string[] args) { Console.WriteLine("Before async task"); await DoAsyncTask(); Console.WriteLine("After async task"); } static async Task DoAsyncTask() { await Task.Run(() => { Console.WriteLine("Async task started"); // 模拟异步操作 Task.Delay(1000).Wait(); Console.WriteLine("Async task finished"); }); } } ``` 在这个示例中,`Main` 方法中先输出 "Before async task",然后调用异步方法 `DoAsyncTask`,等待异步方法执行完毕后输出 "After async task"。在 `DoAsyncTask` 方法中,使用 `Task.Run` 方法异步执行一段代码,其中包含了一个 1 秒钟的延迟操作。运行这个程序,你会发现输出的顺序是: ``` Before async task Async task started Async task finished After async task ``` 这说明异步任务已经成功执行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值