C#异步编程学习笔记2 之 Task

关于 Task 简单梳理(C#)【并发编程系列】 Task 是微软在 .Net 4.0 时代推出来的,也是微软极力推荐的一种多线程的处理方式。在 Task 之前有一个高效多线程操作类 ThreadPool,虽然线程池相对于 Thread,具有很多优势避免频繁创建和销毁线程等,但是线程池也有一些使用上的不便,比如不支持取消、完成、失败通知等,也不支持线程执行的先后顺序配置。为了解决上述痛点,Task 诞生了。Task 就是站在巨人的肩膀上而生,它是。Task 的控制和扩展性很强,在线程的延续、阻塞、取消、超时等方面远胜于 ThreadPool。 阅读详情

Task

Thread的问题

线程(Thread)时用来创建并发(concurrency)的一种低级别工具,它有一些限制,尤其是:

  • 虽然开始线程的时候方便传入数据,但是当 Join 的时候,很难从线程获得返回值。
    • 如果需要返回值,可能需要设置一些共享字段
    • 如果操作抛出异常,捕获和传播异常都很麻烦
  • 无法告诉线程在结束的时候开始做另外的工作,必须进行 Join 操作(在进程中阻塞当前线程)

所以很难使用较小的并发(concurrent)来组建大型的并发,导致了对手动同步的更大依赖以及随之而来的问题。

而 Task 类可以很好的解决上述问题。

Task Class

Task 是一个相对高级的抽象,它代表了一个并发操作(concurrent),该操作可能由 Thread 支持,或不由 Thread 支持。

  • Task 是可组合的(可使用 Continuation 把它们串成链
  • Tasks 可以使用线程池来减少启动延迟
  • 使用 TaskCompletionSource ,Tasks 可以利用回调的方式,在等待 I/O 的绑定操作时完全避免线程
开始一个Task(Task.Run)

Task 类在 System.Threading.Tasks 命名空间下

开始一个 Task 最简单的办法就是使用 Task.Run(.Net 4.5 添加的,.Net 4.0 的使用是 Task.Factory.StartNew)这个静态方法:

  • 使用时传入一个 Action 委托即可

    public static void Main(string[] args)
    {
        Task.Run(() => Console.WriteLine("Foo"));
    }
    
  • Task 默认使用线程池,也就是后台线程。当主线程结束时,创建的所有的 tasks 都会结束。可通过阻塞主线程的方法,来使创建的 tasks 完成执行。

    public static void Main(string[] args)
    {
        Task.Run(() => Console.WriteLine("Foo"));
        Console.ReadLine();
    }
    
  • Task.Run 返回一个 Task 对象,可以使用它来监视其过程。

  • 在 Task.Run 之后,没有调用 Start 方法,因为通过 Task.Run 创建的使“热”任务(host task),即创建完之后就准备开始运行,不需要调用 Start 方法。

    • 可以通过 Task 的构造函数来创建“冷”任务(cold task),但是很少这样做。
  • 可以通过 Task 的 Status 属性来跟踪 task 的执行状态。

Wait 等待

调用 task 的 Wait 方法会进行阻塞直到操作完成,相当于调用 Thread 的 Join 方法。

public static void Main(string[] args)
{
    Task task = Task.Run(() =>
                         {
                             Thread.Sleep(3000);
                             Console.WriteLine("Foo");
                         });

    Console.WriteLine(task.IsCompleted);  //false

    task.Wait();   //阻塞,直到 task 完成操作

    Console.WriteLine(task.IsCompleted);  //true
}

Wait 方法也可以指定一个超时时间和一个取消令牌来提前结束等待。

长时间运行的任务(Long-running tasks)

默认情况下,CLR 在线程池中运行 Task,这非常适合短时间运行的 Compute-Bound 类工作。

针对长时间运行的任务或者阻塞操作,可以不采用线程池的方法。

public static void Main(string[] args)
{
    Task task = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(3000);
                System.Console.WriteLine("Foo");
            }, TaskCreationOptions.LongRunning);
}

如果同时运行多个 long-running tasks(尤其是其中由处于阻塞状态的),那么性能将会受到很大影响,这时由比 TaskCreationOptions.LongRunning 更好的办法:

  • 如果任务是 I/O-Bound,TaskCompletionSource 和异步函数可以用回调(Coninuations)代替线程来实现并发。(杨老师说后续填坑)
  • 如果任务是 Compute-Bound,生产者/消费者队列允许对任务的并发性进行限流,避免把其它线程和进程饿死。(并发编程,后续了解)
Task 的返回值

Task 有一个泛型子类叫做 Task<TResult> ,它允许发出一个返回值。

使用 Func<TResult> 委托或者兼容的 Lambda 表达式来调用 Task.Run 就可以得到 Task<TResult>。

随后,可以通过 Result 属性来获得返回的结果。如果,这个 task 还没有完成操作,访问 Result 属性会阻塞该线程直到该 task 完成操作。

public static void Main(string[] args)
{
    Task<int> task = Task.Run(() =>
            {
                System.Console.WriteLine("Foo");
                return 3;
            });
    int result = task.Result;  //如果 task 没完成,那么就阻塞
    Console.WriteLine(result);
}

Task<TResult> 可以看作是一种所谓的“未来/许诺”(future、promise),在它里面包裹这一个 Result,在稍后的时候就会变得可用。在 CTP 版本的时候,Task<TResult> 就叫做 Future<TResult> 。

Task 的异常

Task 与 Thread 不一样,Task 可以很方便的传播异常。

  • 如果 task 里面抛出了一个未处理的异常(故障),那么该异常就会重新抛出给:

    • 调用了 wait() 的地方

    • 访问了 Task<TResult> 的 Result 属性的地方

      public static void Main(string[] args)
      {
          Task task = Task.Run(() => { throw null; });
          try
          {
              task.Wait();  //异常抛出到此处
          }
          catch(AggregateException aex)
          {
              if(aex.InnerException is NullReferenceException)
              {
                  Console.WriteLine("Null");
              }
              else
              {
                  throw;
              }
          }
      }
      

      输出结果:

      ​ Null

      CLR 将异常包裹在 AggregateException 里,一边在并行编程场景中发挥很好的作用。

  • 当无需抛出异常时,通过 Task 的 IsFaulted 和 IsCanceled 属性也可以检测出 Task 是否发生了故障:

    • 如果两个属性都返回 false,那么没有错误发生。
    • 如果 IsCanceled 为 true,那就说明一个 OperationCanceledException 为该 Task 抛出了。
    • 如果 IsFaulted 为 true,那就说明另一个类型的异常被抛出了,而 Exception 属性也将指明错误。
异常与“自治”的Task
  • 自治的,”设置完就不管了“的Task。就是指不通过调用 Wait() 方法、Result 属性或 continuation 进行合并的任务。
  • 针对自治的 Task,需要像 Thread 一样,显式的处理异常,避免发生”悄无声息的异常“
  • 自治 Task 上未处理的异常成为未观察到的异常。
未观察到的异常
  • 可以通过全局的 TaskScheduler.UnobservedTaskException 来订阅未观察到的异常。
  • 关于什么是”未观察到的异常“,由一些细微的差别:
    • 使用超时进行等待的 Task,如果在超时后发生故障,那么它将会产生一个”未观察到的异常“
    • 在 Task 发生故障后,如果访问 Task 的Exception 属性,那么该异常就被认为是“已观察到的”

Continuation

一个 Continuation 会对 Task 说:“当你结束的时候,继续做点其它的事。”

Continuation 通常是通过回调的方式实现的:

  • 当操作一结束,就开始执行

    public static void Main(string[] args)
    {
        Task<int> primeNumberTask = Task.Run(() =>
    		Enumerable.Range(2, 3000000).Count(n => 
            	Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
        
        var awaiter = primeNumberTask.GetAwaiter();
        awaiter.OnCompleted(() =>
        {
            int result = awaiter.GetResult();
            Console.WriteLine(result);
        });
        Console.ReadLine();  //阻塞应用进程
    }
    
  • 在 task 上调用 GetAwaiter 会返回一个 awaiter 对象。它的 OnCompleted 方法会告诉之前的 task:“当你结束/发生故障的时候要执行委托”。

  • 可以将 Continuation 附加到已经结束的 task 上面,此时 Continuation 将会被安排立即执行。

awaiter

任何可以暴露下列两个方法和一个属性的对象就是 awaiter:

  • OnCompleted() 方法
  • GetResult() 方法
  • IsCompleted 属性,该属性为 bool 类型

awaiter没有接口或者父类来统一这些成员。其中 OnCompleted 是 INotifyCompletion 接口的一部分。

如果发生故障

如果之前的任务发生故障

  • 当 Continuation 代码调用 awaiter.GetResult() 的时候,异常会被重新抛出。
  • 当无需调用 GerResult 时,我们可以直接访问 task 的 Result 属性。
  • 但调用 GetResult 的好处是,如果 task 发生故障,异常会被直接地抛出,而不是包裹在 AggregateException 里面,这样的话 catch 语句块就简介很多了。
非泛型task

针对非泛型的 task,GetResult() 方法有一个 void 返回值,它仅用来重新抛出异常。

同步上下文

如果同步上下文出现了,那么 OnCompleted 会自动捕获它,并将 Continuation 提交到这个上下文中。这一点在富客户端应用中非常有用,因为它会把 Continuation 放回到 UI 线程中。

如果是编写的一个库,则不希望出现上述行为,因为开销较大的 UI 线程切换应该在程序运行离开库的时候只发生一次,而不是出现在方法调用之间。所以,我们可以使用 ConfigureAwait 方法来避免这种行为。

public static void Main(string[] args)
{
    Task<int> primeNumberTask = Task.Run(() =>
		Enumerable.Range(2, 3000000).Count(n => 
        	Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
    
    var awaiter = primeNumberTask.ConfigureAwait(false).GetAwaiter();
    awaiter.OnCompleted(() =>
    {
        int result = awaiter.GetResult();
        Console.WriteLine(result);
    });
    Console.ReadLine();  //阻塞应用进程
}

如果没有同步上下文出现,或者使用的是 ConfigureAwait(false),那么 Continuation 会运行在先前 task 的同一个线程上,从而避免不必要的开销。

ContinueWith

另外一种附件 Continuation 的方式就是调用 task 的 ContinueWith 方法

public static void Main(string[] args)
{
    Task<int> primeNumberTask = Task.Run(() =>
		Enumerable.Range(2, 3000000).Count(n => 
        	Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
    
    primeNumberTask.ContinueWith(task =>
    {
        int result = awaiter.GetResult();
        Console.WriteLine(result);
    });
    Console.ReadLine();  //阻塞应用进程
}

ContinueWith 本身返回一个 task,它可以用它来附加更多的 Continuation。但是,必须直接处理 AggregateException:

  • 如果 task 发生故障,需要写额外的代码来把 Continuation 封装(marshal)到 UI 应用上。
  • 在非 UI 上下文中,若想让 Continuation 和 task 执行在同一个线程上,必须指定 TaskContinuationOptions.ExecuteSynchronously,否则它将弹回到线程池。

ContinueWith 对于并行编程来说是非常有用的。

TaskCompletionSource

创建 Task 除了通过 Task.Run(),还可以使用 TaskCompletionSource 来创建。

TaskCompletionSource 可以在稍后开始和结束的任意操作中创建 Task

  • 它会提供一个可手动执行的“从属” Task,可指示操作何时结束或发生故障

TaskCompletionSource 对于 IO-Bound 类工作比较理想

  • 可以获得所有 Task 的好处(传播值、异常、Continuation等)
  • 不需要在操作时阻塞线程
使用

使用TaskCompletionSource初始化一个实例即可。它有一个Task属性可返回一个Task 。该Task完全由TaskCompletionSource 对象控制 。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G6rTubgm-1604878145491)(C:\Users\86183\Desktop\学习笔记\图片\image-20201105234308773.png)]

TaskCompletionSource 中有如上图的方法,调用人一个方法都会给 Task 发信号(故障、完成、取消)。这些方法只能调用一次,如果再次调用,则:

  • SetXxx 会抛出异常
  • TryXxx 会返回 false
public static void Main(string[] args)
{
    var tcs = new TaskCompletionSource<int>();
    
    new Thread(() =>
    {
        Thread.Sleep(5000);
        tcs.SetResult(42);
    })
    {
        IsBackground = true;
    }.Start();
    
    Task<int> task = tcs.Task;
    Console.WriteLine(task.Result);
}

自定义 Task.Run() 方法

public static void Main(string[] args)
{
    Task<int> task = Run(() =>
                         {
                             Thread.Sleep(5000);
                             return 42;
                         });
}

//调用这个方法相当于调用 Task.Factory.StartNew
//并使用 TaskCreationOptions.LongRunning 选项来创建非线程池的线程
static Task<TResult> Run<TResult>(Func<TResult> function)
{
    var tcs = new TaskCompletionSource<TResult>();
    new Thread(() =>
               {
                   try
                   {
                       tcs.SetResult(function());  //运行 function 委托
                   }
                   catch(System.Exception ex)
                   {
                       tcs.SetException(ex);
                   }
               }).Start();
    return tcs.Task;
}
TaskCompletionSource的真正魔力

TaskCompletionSource 创建 Task,但并不占用线程。

public static void Main(string[] args)
{
    var awaiter = GetAnswerToLife().GetAwaiter(); //生成一个 Continuation
    awaiter.OnCompleted(() => 
    {
        Console.WriteLine(awaiter.GetResult());
    });
    Console.ReadKey();
}

//调用这个方法相当于调用 Task.Factory.StartNew
//并使用 TaskCreationOptions.LongRunning 选项来创建非线程池的线程
static Task<int> GetAnswerToLife()
{
    var tcs = new TaskCompletionSource<int>();
    
    var timer = new System.Timers.Timer(5000) { AutoReset = false };
    timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(42); };
    timer.Start();
    return tcs.Task;
}

将上述代码封装成更加通用的方法:

public static void Main(string[] args)
{
    Delay(5000).GetAwaiter().OnCompleted(() => Console.WriteLine(42));
    // 5秒钟之后,Continuation 开始的时候,才占用线程
}

//注意:因为没有非泛型版本的 TaskCompletionSource,此处用object妥协
static Task Delay(int milliseconds)
{
    var tcs = new TaskCompletionSource<object>();    
    var timer = new System.Timers.Timer(milliseconds) { AutoReset = false };
    timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(42); };
    timer.Start();
    return tcs.Task;
}
Task.Delay

.net Core 自带方法,相当于异步版本的 Thread.Sleep 方法

public static void Main(string[] args)
{
    Delay(5000).GetAwaiter().OnCompleted(() => Console.WriteLine(42));
    // 5秒钟之后,Continuation 开始的时候,才占用线程
    
    Task.Delay(5000).GerAwaiter().OnCompleted(() => Console.WriteLine(42));
    Task.Delay(5000).ContinueWith(ant => Console.WriteLine(42));
    // Task.Delay 相当于异步版本的 Thread.Sleep
}

//注意:因为没有非泛型版本的 TaskCompletionSource,此处用object妥协
static Task Delay(int milliseconds)
{
    var tcs = new TaskCompletionSource<object>();    
    var timer = new System.Timers.Timer(milliseconds) { AutoReset = false };
    timer.Elapsed += delegate { timer.Dispose(); tcs.SetResult(42); };
    timer.Start();
    return tcs.Task;
}
C#任务Task应用详解 1、引用空间 System.Threading.Task 2、定义 Task类可以更好的控制并行操作,任务表示将完成的某个工作单元,该工作单元可以在单独的线程运行,可以以同步方式启动一个新任务。 3、启动任务的方式 任务处理程序 public static void TaskMethod(string o){ Console.WriteLine(“进入任务处理程序”+o); } (1)、使用实例化的TaskFactory类,在其中将TaskMethod方法传递给StartNew方法 var tf=new T 阅读详情

相关推荐

C# 异步编程详解(Task,async/await)

同步和异步主要用于修饰方法。当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;当一个方法被调用时立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。

luxingyu329的博客 4154

C#线程——Task.Run()方法的使用

Task.Run()是Task类中的一个类,作用是:将指定的工作加入到到ThreadPool队列上运行,并为该工作返回一个Task或Task < result >句柄。具有多个重载方法,

weixin_59555766的博客 1万+

C# Task异步编程

Task任务用法:Task用的是线程池,线程池的线程数量的有上限的,这个可以通过ThreadPool修改,我们经常会用到task.run ,new task ,和task.factory.startnew方法来创建任务。Task.Factory.StartNew(action)不是直接创建线程,创建的是任务,它有一个任务队列,然后通过任务调度器把任务分配到线程池中的空闲线程中,任务是不能被直接执行的

努力发光呀 2万+

.Net C# Task 任务

.Net C# Task 任务

u010992421的专栏 7429

C#中的Task怎么理解,理解异步编程的核心

C#异步编程、Task用法、async/await、并发处理、性能优化

逆流而上的技术学习,真诚携手共进之陪伴的博客 1006

一文吃透 C#中异步编程Task

本文全面介绍了C#中的异步编程Task核心知识。Task是.NET 4引入的异步操作类,基于线程池自动调度,具有异步执行、状态可控和支持返回值等特点。文章推荐了Task的适用场景:复杂计算、多任务并行和后台任务。通过多个Demo详细演示了Task.Run启动任务、FromResult包装结果、Wait/WaitAll/WaitAny等待任务、以及任务取消与异常处理等核心用法。最后提供了常用API速查表,包括Task.Run、WaitAll、WaitAny等关键方法的使用说明。本文涵盖Task从基础到进阶的完

qq_34552942的博客 1123

学懂C#异步编程:深入详解 C# 异步方法中的 Task.WhenAll、Task.WhenAny、await、Wait、WaitAll、WaitAny 等用法及其区别和需要注意的坑

学懂C#异步编程:深入详解 C# 异步方法中的 Task.WhenAll、Task.WhenAny、await、Wait、WaitAll、WaitAny 等用法及其区别和需要注意的坑

编程技术探索者,分享C/C++、C#、Java、数据库等开发经验,聚焦实战技巧与AI兴趣,助力编程爱好者成长。 6511

C#异步编程:从线程到Task的进化之路

本文系统解析了异步编程的演进与技术实现。同步编程模型因阻塞执行、资源浪费等问题催生了异步编程,其中async/await通过状态机机制显著提升了代码可读性。深入探讨了C#中Task与Thread的差异、硬件层面的线程实现原理,以及.NET中异步的底层机制(包括JIT编译和异常处理)。异步编程优化了I/O密集型应用性能,但需权衡约80字节的编译开销。技术演进体现了从同步阻塞到高效并发的编程范式转变。

weixin_45649283的博客 1083

C# Task 异步编程详解

所有按钮事件写成 async Task(绝不 async void)所有耗时操作一律 await,不要 .Result / .Wait()更新控件一律 await this.InvokeAsync(…)所有循环采集必须支持 CancellationToken长时间操作放 Task.Run + IProgress全局捕获 TaskScheduler.UnobservedTaskException。

专注于前沿技术的探索与实践,热衷于从代码逻辑、架构设计到行业应用等多维度深挖技术本质。期待在技术之路上与你共同成长~ 1562

C# 异步编程 并行编程

目录 异步编程 1. Thread 2. Task 3.async + await 并行编程 Parallel 本篇简单记录一下异步编程和并行编程,并以下图一个 Demo 来演示,源码地址我会公布在文章结尾。 异步编程 个人觉得,异步无非就是不影响某个线程的情况下,在另外的线程里执行任务的过程。异步编程即多线程编程。C# 里多线程编程写法有很多种,这里记录三种。 ...

sudazf的专栏 9687

C#异步编程致命陷阱:Task.Run的10个坑与代码级深度解析

在高并发场景中,频繁调用Task.Run可能引发线程池资源枯竭。:在UI线程中使用Task.Run后,试图直接操作UI控件。:在Parallel.ForEach中滥用Task.Run。:事件处理程序使用async void引发异常不可捕获。:Task.Run中的异常未被捕获,引发未处理异常。:未正确释放异步操作中的资源。:在异步代码中频繁操作值类型。:自定义调度器未正确实现。

墨夶的博客 2294

C#笔记14 异步编程Async,await,task类

我们说过,当我们实现了多线程,我们就拥有了分别执行多个代码块的能力,如果他们执行各自的逻辑,互相不存在等待和阻塞的话,就是完全的异步,但是我们往往会遇到多个线程同时访问或者请求同一个资源的情况下,如果任由他们执行,明显可能会导致数据的错误和资源的抢占,那么为我们就使用了同步的方法来控制线程之间的协作。异步编程就是又回到了之前的问题,我们知道我们线程本身是异步的,但是由于共享资源和共享代码等需要同步机制,所以进行等待。

wuyi的个人博客 2073

C# 中的Task

在 C# 中,Task代表一个异步操作。它允许你在不阻塞主线程的情况下执行耗时的操作,从而提高应用程序的响应性和性能。本教程将详细介绍 C# 中的Task。Task是 C# 中一种强大的异步编程模型,它允许你在不阻塞主线程的情况下执行耗时的操作。通过使用Task,你可以提高应用程序的响应性和性能,同时保持代码的简洁和可读性。

2401_83367969的博客 5711

C#中的Task:异步编程的瑞士军刀

C#中的Task:异步编程的瑞士军刀

qq_35320456的博客 2614

实验手册(灾难恢复)

我们发现DC1与DC2的复制出现问题,此时我们检查了在DC2中,DC1是否被正确注册。这里选择还原系统状态,因为我们原DC1上只备份了AD的数据,所以我们在新的这台服务器上还原的也是AD数据库。因为我们是单域下的测试,只测试了一个域,所以它会出现这样的报错。我们选择远程共享文件夹,我们备份的时候,是在备份这台服务器上的。同时删除SRV记录,A记录,NS记录,SOA记录,框框里面的所有有关dc1的记录全部删除。如果在企业中AD数据的备份是每周或者每月都会有一次备份,选择恢复AD数据库的时间。

weixin_52364868的博客 1310

应急响应之windows日志分析工具logparser使用

windows日志分析工具logparser使用介绍

u013930899的博客 8126

C#异步编程基础入门总结

异步这概念刚开始接触的时候,不是那么容易接受,但是需要用的地方还真的挺多的,刚学习的时候,也很懵逼走了不少弯路,所以这里有必要总结一下。

dotNet全栈开发 2万+

获取上传至DMS服务器上文件的URL

如果需要失效时间延长会设置URL永久有效, 可以去修改函数SCMS_URL_GENERATE的参数DURATION 或EXPIRATION ,DURATION最大时间是999999,若需要永久有效,则需要设置EXPIRATION的时间,这时系统会忽略DURATION的参数,生成URL的时候 SCMS_URL_GENERATE会调用SCMS_URL_SIGN函数这是其中默认这是了URL的生效时间为两小时。

一只学习的渣渣 542

C#——多线程之Task

C#——多线程之Task

我叫DP的博客 1万+

功率电感的设计步骤

学习视频原链接截图1截图2。

qq_32419593的博客 4887

Python + Playwright:集成 Applitools 进行视觉回归测试(快速入门)

Applitools是一款基于人工智能的视觉验证测试工具,专门用于自动化和验证应用程序的用户界面(UI);Applitools使用视觉回归测试来检测应用程序界面的变化,确保 UI 在不同版本之间的一致性;Applitools的核心功能是通过视觉比较,检测 UI 元素的差异,而无需手动编写复杂的界面检查代码;通过集成Applitools,测试人员可以轻松捕捉和验证界面中的视觉差异,无论是在Web移动端或桌面应用的测试环境;

blues_C的博客 2921

C#异步编程 - Task的用法总结

C#中的Task是异步编程的核心类型,封装了异步操作的状态和行为。Task有7种状态,包括创建、等待执行、运行中、完成等。创建Task的方式包括Task.Run()、async/await、Task.FromResult等。等待任务可用await(推荐)或阻塞式Wait()/Result。处理多个任务可使用WhenAll/WhenAny或WaitAll/WaitAny。异常处理上,await会自动解包异常,而直接访问Task需处理AggregateException。取消操作通过CancellationTo

GuoGuoABC 951

Android 13 Settings蓝牙列表卡顿问题排查及优化过程

此时我们的卡顿优化基本完成了,我们应该要考虑内存是否会泄漏,如果大家不知道哪些需要回收可以用leakcanary和android profiler进行内存检测,看代码有没有内存泄漏。

龚礼鹏的博客 2547
上一篇: C#异步编程学习笔记1 之 线程Thread
下一篇: C#异步编程学习笔记3 之 异步原理
SQWH_SSGS
博客等级 码龄9年 61粉丝 49原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值