c# 任务(Task)以及异步 await(等待) 、Async(异步)

本文详细介绍了C#中的Task类如何利用线程池提高效率,对比了Task与Thread和ThreadPool的差异,并讲解了如何创建、启动、等待和取消Task,以及await和异步方法的使用和异常处理。
摘要由CSDN通过智能技术生成

任务(Task)

Task作为C#异步的核心,Task位于System.Threading.Tasks命名空间下。

创建任务的基本原理是使用线程池,也就是说任务最终也是要交给线程去执行的。但是微软优化了任务的线程池,使线程的控制更加精准和高效.

task默认是后台线程,而thread默认是前台线程.可以设置task为前台线程,Thread.CurrentThread.IsBackground,将IsBackground设置为false即可.

Task出现之前,微软的多线程处理方式有:Thread→ThreadPool→委托的异步调用,虽然也可以基本业务需要的多线程场景,但它们在多个线程的等待处理方面、资源占用方面、线程延续和阻塞方面、线程的取消方面等都显得比较笨拙,ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:

ThreadPool不支持线程的取消、完成、失败通知等交互性操作;

ThreadPool不支持线程执行的先后次序;
Task在ThreadPool的基础上进行的封装,Task的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于Thread和ThreadPool。

新建并启动task:
方式1:TaskFactory工厂

如果你想建立一个Task并且立即执行它,使用Task.Factory.StartNew(),这样做性能更好。

Task.Factory.StartNew(() =>

{

Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);

});

//带有参数,返回值任务

Task<int>.Factory.StartNew(new Func<object, int>(test3), i);

static int test3(object obj)

{

    int i = (int)obj;

    Thread.Sleep(3000);

    return i + i;

}

方式2:调用Start方法

如果你想建立一个Task,想在合适的时候执行它,那么使用Task构造器做初始化,使用Start函数启动任务。

//不带返回值,无参数的任务

Task task = new Task(() =>

{

    Console.WriteLine("Main1 Thread" + Thread.CurrentThread.ManagedThreadId);

});

task.Start();

//带返回值,无参数的任务

Task<int> task1 = new Task<int>(() =>

{

    Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);

     return 0;

});

task1.Start();

//带有参数,无返回值任务

Action<object> action = (a) =>

{

    Console.WriteLine("The delegate method is executed,state is :" + a);

};

object obj2 = "success";

Task task2 = new Task(action, obj2);

task1.Start();

//带有参数,返回值任务

object i = 5;

Task<int> task3 = new Task<int>(new Func<object, int>(test3),i);

task3.Start();

static int test3(object obj)

{

    int i = (int)obj;

    Thread.Sleep(3000);

    return i + i;

}

方式3:静态方法Run

//将任务放在线程池队列,返回并启动一个Task.task无参数,无返回值

Task task4 = Task.Run(() =>

{

    Console.WriteLine("Main4 Thread" + Thread.CurrentThread.ManagedThreadId);

 });

 //将任务放在线程池队列,返回并启动一个Task.task无参数,带返回值

        static int test5()

        {   

            Thread.Sleep(3000);

            return 0;

        }

 Task<int> task5 = Task.Run<int>(new Func<int>(test5));

求返回值

当task有返回值时,可通过public TResult Result { get; }  Result 字段获取返回值。

如:int  nret= task5.Result ;

使用Result 字段,会导致当前线程阻塞,直到task结束返回。

延伸到await :await 后面代码会阻塞,但当前线程不会阻塞。

延迟时间

public static Task Delay(TimeSpan delay)

等待task:

使用wait或WaitAll、WaitAny函数等待,可以设置等待时间。

Task task = new Task(() =>

{

    Console.WriteLine("Main1 Thread" + Thread.CurrentThread.ManagedThreadId);

});

task.Start();

task.Wait(1000);

WaitAll为静态函数,可以设置等待多个任务结束,所有任务结束才返回。

Task<int> task1 = new Task<int>(() =>

{

    Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);

     return 0;

});

task1.Start();

Task.WaitAll(task,task1 );

WaitAny静态函数,可以设置等待多个任务结束,只要一个任务结束就返回。

Task<int> task1 = new Task<int>(() =>

{

    Console.WriteLine("Main2 Thread" + Thread.CurrentThread.ManagedThreadId);

     return 0;

});

task1.Start();

Task.WaitAny(task,task1 );

WhenAny

WhenAny:与ContinueWith配合,线程列表中任何一个执行完毕,则继续ContinueWith中的任务(开启新线程,不阻塞主线程)

Action<string,int> log = (name,time) =>

    {

        Console.WriteLine($"{name}任务开始...");

        Thread.Sleep(time);

        Console.WriteLine($"{name}任务结束!");

    };

    List<Task> tasks = new List<Task>

    {

        Task.Run(() => log("张三",3000)),

        Task.Run(() => log("李四",1000)),

        Task.Run(() => log("王五",2000))

};

Task.WhenAny(tasks.ToArray()).ContinueWith(x => Console.WriteLine("某个Task执行完毕"));

连续任务

可以指定在任务完成之后,应开始运行之后另一个特定任务。ContinueWith是Task根据其自身状况,决定后续应该作何操作。

            var testTask = new Task(() =>

            {

                Console.WriteLine("task start");

                System.Threading.Thread.Sleep(2000);

            });

            testTask.Start();

            var resultTest = testTask.ContinueWith<string>( task6 => {

                Console.WriteLine("testTask end");

                return "end";

            });

            Console.WriteLine(resultTest.Result);

WhenAll

Task.WhenAll用于等待所有提供的Task对象完成执行。这个方法返回一个新的Task,这个Task将在所有提供的Task完成后完成。如果任何一个Task失败,Task.WhenAll返回的Task也将以异常状态完成,抛出AggregateException。这个方法非常适合在你需要并行执行多个操作,并且需要等待所有操作完成后才能继续执行的情况。

WhenAll是一个非阻塞方法,不会阻塞当前线程,它会立即返回一个Task,这个Task在所有的任务都完成时完成。

WaitAll

WaitAll是一个阻塞方法,意味着它会阻塞当前的线程直到所有的任务都完成。WaitAll通常用于控制台应用程序或者需要阻塞当前线程等待所有任务完成的场景。

取消任务

使用CancellationTokenSource:创建一个CancellationTokenSource对象,并将其传递给要取消的任务。然后,调用CancellationTokenSource的Cancel方法来触发任务的取消。

            var tokenSource = new CancellationTokenSource();

            var token = tokenSource.Token;

            var task7 = Task.Factory.StartNew(() =>

            {

                    System.Threading.Thread.Sleep(4000);

                    if (token.IsCancellationRequested)

                    {

                        Console.WriteLine("Abort mission success!");

                        return;

                    }   

            }, token);

            //注册cancel后要执行的代码

            token.Register(() =>

            {

                Console.WriteLine("Canceled");

            });

            Console.WriteLine("Press enter to cancel task...");

            //调用取消

            tokenSource.Cancel();

异步方法的异常处理

步方法异常的一个较好处理方式是使用 await 关键字,将其放在 try/catch 语句中。

private static async void HandleOneError()

{

  try

  {

    await ThrowAfter(2000, "first");

  }

  catch (Exception ex)

  {

    Console.WriteLine($"handled {ex.Message}");

  }

}

异步调用 ThrowAfter 方法后,HandleOneError 方法就会释放线程,但它会在任务完成时保持任务的引用。此时(2s 后,抛出异常),会调用匹配的catch 块内的代码。

多个异步方法的异常处理

   private async static void CatchException()

        {

            Task taskException = null;

            try

            {

                Task t1 = TestException("first");

                Task t2 = TestException("second");

                await (taskException = Task.WhenAll(t1, t2));//不管任务是否抛出异常,都会等到所有任务完成

            }

            catch (Exception ex)

            {

                Console.WriteLine($"Catch Exception:{ex.Message}");//在这里只能捕捉到第一个Task t1 的异常信息

                foreach (var item in taskException.Exception.InnerExceptions)

                {

                    Console.WriteLine($"InnerExceptions:{item.Message}");

                }

            }

        }

        private async static Task TestException(string message)

        {

            Console.WriteLine($"{DateTime.Now.ToString()}:<{message}> TestException is running");

            await Task.Delay(2000);

            throw new Exception($"{DateTime.Now.ToString()}:<{message}> Throw test exception");

        }

 await(等待) Async(异步)

 使用场景

即在一个函数体func1内,调用返回任务的异步方法。调用某方法返回值为Task,方法前加上await。如:HttpResponseMessage response = await httpClient.PostAsync(strUrl, content);

特点:

在该异步方法完成前,该函数体func1内await后面的代码不会继续执行。但是执行该函数体func1的线程不会被阻塞。那就是函数体func1不同生命阶段使用了不同的线程。

传染性

函数体中有await 调用 ,该函数体修饰必须为asyns,也就说明该函数为异步方法。无返回值,用非泛型的Task。有返回值一般是 Task<T>

 public static async Task<string>  GetTokenname(string strUrl, Dictionary<string, string> kVDatas)

        {

            string strtoken = "";

            try

            {

                var content = new MultipartFormDataContent();

                foreach (string key in kVDatas.Keys)

                {

                    content.Add(new StringContent(kVDatas[key]), key);

                }

                using (var httpClient = new HttpClient())

                {

                    using (HttpResponseMessage response = await httpClient.PostAsync(strUrl, content))

                    {

                        if (response.IsSuccessStatusCode)

                        {

                            var str = await response.Content.ReadAsStringAsync();

                            JObject obj = (JObject)JsonConvert.DeserializeObject(str);//将刚才一大串字符串转换成一个大对象

                            string strSuc = obj["success"].ToString();

                            if (strSuc.Equals("true", StringComparison.OrdinalIgnoreCase))

                            {

                                strtoken = obj["data"]["token"].ToString();

                            }

                        }

                    }

                }

            }

            catch (Exception e)

            {

                Logger.WriteError("获取token" + e.Message);

                return "";

            }   

            return strtoken;

        }

延伸:求任务的返回值

当task有返回值时,可通过public TResult Result { get; }  Result 字段获取返回值。

如:

      static int test5()

        {   

            Thread.Sleep(3000);

            return 0;

        }

Task<int> task5 = Task.Run<int>(new Func<int>(test5));

int  nret= task5.Result ;

使用Result 字段,会导致当前线程阻塞,直到task结束返回。

Func<T>:为有返回值的泛型委托(必须有返回值),Func可以无参数,也可以有参数,最多16个参数,最后一个表示返回值且只有一个

  • 13
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值