C# - Task的各种用法和详解

1、Task简介【*所有的线程任务都会随着主线程的退出而退出】

  • ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便。比如:
    • ThreadPool不支持线程的取消、完成、失败通知等交互性操作;
    • ThreadPool不支持线程执行的先后次序;
  • 以往,如果开发者要实现上述功能,需要完成很多额外的工作,现在,FCL中提供了一个功能更强大的概念:Task。Task在线程池的基础上进行了优化,并提供了更多的API。在FCL4.0中,如果我们要编写多线程程序,Task显然已经优于传统的方式。
  • 以下是一个简单的任务示例:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Task1

    {

        public void TaskMethod1()

        {

            Task t = new Task(() =>

            {

                Console.WriteLine("任务开始...");

                Thread.Sleep(5000);

            });

            t.Start();

            t.ContinueWith((task) =>

            {

                Console.WriteLine("任务完成,完成时的状态为:");

                Console.WriteLine("IsCanceled={0}\tIsCompleted={1}\tIsFaulted={2}", task.IsCanceled, task.IsCompleted, task.IsFaulted);

            });

        }

    }

}

2、Task用法【new只是创建了一个任务,需要Start才会执行,Task.Run是直接开始执行】

2.1创建任务

  • 不带返回参数的

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Task2

    {

        public void TaskMethod1(string param)

        {

            Console.WriteLine($"输入参数:{param}");

        }

        public void TaskMethod2()

        {

            //方式1

            var t1 = new Task(() => TaskMethod1("无返回值方式1.1"));

            var t2 = new Task(() => TaskMethod1("无返回值方式1.2"));

            t1.Start();

            t2.Start();

            Task.WaitAll(t1, t2);//会等待所有任务结束,主线程才会退出

            //方式2

            Task.Run(() => TaskMethod1("无返回值方式2"));

            //方式3

            Task.Factory.StartNew(() => TaskMethod1("无返回值方式3"));//异步方法

            //or

            Task t3 = Task.Factory.StartNew(() => TaskMethod1("无返回值方式3"));

            t3.Wait();

        }

    }

}

  • async/await的实现方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Task2

    {

        /// <summary>

        /// async/await的实现方式:

        /// </summary>

        public async void TaskMethod3()

        {

            //Task.Delay方法只会延缓异步方法中后续部分执行时间,当程序执行到await表达时,一方面会立即返回调用方法,执行调用方法中的剩余部分,这一部分程序的执行不会延长。另一方面根据Delay()方法中的参数,延时对异步方法中后续部分的执行。

            await Task.Delay(1000);

            Console.WriteLine("执行异步方法");

            for (int i = 0; i < 10; i++)

            {

                Console.WriteLine(i);

            }

        }

    }

}

  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

using System;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            //Task1 task1 = new Task1();

            //task1.TaskMethod1();

            Task2 task2 = new Task2();

            Console.WriteLine("主线程执行其他任务...");

            task2.TaskMethod3();

            Console.WriteLine("主线程执行其他处理...");

            for (int i = 0; i < 10; i++)

            {

                Console.WriteLine("主线程{i}");

            }

            Console.ReadKey();

        }

    }

}

  • 带返回值得方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    public class Task3

    {

        int TestMethod1()

        {

            Console.WriteLine("测试方法");

            Thread.Sleep(2000);

            return 12;

        }

        public Task<int> TaskMethod1()

        {

            return Task.Run(() => TestMethod1());//Task<int>.Run(() => TestMethod1()); 简化了<int>

        }

        public int TestMethod2()

        {

            int sum = 0;

            Console.WriteLine("执行异步操作");

            for (int i = 0; i < 100; i++)

            {

                sum += i;

            }

            Thread.Sleep(1000);

            return sum;

        }

    }

}

  

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            //方式1

            Task3 task3 = new Task3();

            Task<int> task = task3.TaskMethod1();

            int result = task.Result;//方法执行完,主线程才会结束

            Console.WriteLine($"1 = {result}");

            //方式2

            task = task3.TaskMethod1();

            Console.WriteLine(task.Status);

            while (!task.IsCompleted)

            {

                Console.WriteLine(task.Status);

                Thread.Sleep(200);

            }

            Console.WriteLine(task.Status);

            result = task.Result;

            Console.WriteLine($"2 = {result}");

            //方式3

            Task<int> task2 = Task.Run(() => task3.TestMethod2());

            Console.WriteLine("主线程执行其他操作");

            //task2.Wait();Result为必须执行完,主线程才结束,所以这里不写Wait不影响

            Console.WriteLine($"3 = {task2.Result}");

        }

    }

}

  •  async/await方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            var task = AsyncMethod1();

            Console.WriteLine("主线程执行其他处理");

            for (int i = 0; i < 3; i++)

            {

                Console.WriteLine($"Main{i}");

            }

            int result = task.Result;

            Console.WriteLine(result);

        }

        async static Task<int> AsyncMethod1()

        {

            await Task.Delay(1000);//立刻返回执行调用方法的后续部分,延迟1秒之后后面的部分

            int sum = 0;

            Console.WriteLine("使用Task执行异步操作.");

            for (int i = 0; i < 1000; i++)

            {

                sum += i;

            }

            return sum;

        }

    }

}

2.2组合任务ContinueWith

  • 简单demo

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

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

            {

                int sum = 0;

                Console.WriteLine("任务1");

                for (int i = 0; i < 100; i++)

                {

                    sum += i;

                }

                return sum;

            });

            task.Start();

            Console.WriteLine("主线程执行其他任务");

            Task task1 = task.ContinueWith(t =>

            {

                Thread.Sleep(1000);

                Console.WriteLine($"task = {t.Result}");

            });

            task1.Wait();//task.Wait();对t.Result不起作用,主线程会直接结束

        }

    }

}

  •  任务的串行【没有研究】

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

using System;

using System.Collections.Concurrent;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            ConcurrentStack<int> stack = new ConcurrentStack<int>();

            //t1先串行

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

            {

                stack.Push(1);

                stack.Push(2);

            });

            //t2,t3并行执行

            var t2 = t1.ContinueWith(t =>

            {

                int result;

                stack.TryPop(out result);

                Console.WriteLine("Task t2 result={0},Thread id {1}", result, Thread.CurrentThread.ManagedThreadId);

            });

            //t2,t3并行执行

            var t3 = t1.ContinueWith(t =>

            {

                int result;

                stack.TryPop(out result);

                Console.WriteLine("Task t3 result={0},Thread id {1}", result, Thread.CurrentThread.ManagedThreadId);

            });

            //等待t2和t3执行完

            Task.WaitAll(t2, t3);

            //t7串行执行

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

            {

                Console.WriteLine("当前集合元素个数:{0},Thread id {1}", stack.Count, Thread.CurrentThread.ManagedThreadId);

            });

            t4.Wait();

        }

    }

}

  •  子任务

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

using System;

using System.Collections.Concurrent;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            Task<string[]> task = new Task<string[]>(state =>

            {

                Console.WriteLine("父任务开始");

                string[] result = new string[2];

                new Task(() => { result[0] = "子任务1"; }, TaskCreationOptions.AttachedToParent).Start();

                new Task(() => { result[1] = "子任务2"; }, TaskCreationOptions.AttachedToParent).Start();

                Thread.Sleep(1000);

                return result;

            }, "我是父任务,创建了子任务,等子任务执行完才会执行结束");

            task.ContinueWith(t =>

            {

                Array.ForEach(t.Result, r => Console.WriteLine(r));

            });

            task.Start();

            task.Wait();

        }

    }

}

  • 动态并行(TaskCreationOptions.AttachedToParent) 父任务等待所有子任务完成后 整个任务才算完成【没有研究过】

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Node

    {

        public Node Left { getset; }

        public Node Right { getset; }

        public string Text { getset; }

    }

    class Program

    {

        static Node GetNode()

        {

            Node root = new Node

            {

                Left = new Node

                {

                    Left = new Node

                    {

                        Text = "L-L"

                    },

                    Right = new Node

                    {

                        Text = "L-R"

                    },

                    Text = "L"

                },

                Right = new Node

                {

                    Left = new Node

                    {

                        Text = "R-L"

                    },

                    Right = new Node

                    {

                        Text = "R-R"

                    },

                    Text = "R"

                },

                Text = "Root"

            };

            return root;

        }

        static void Main(string[] args)

        {

            Node root = GetNode();

            DisplayTree(root);

        }

        static void DisplayTree(Node root)

        {

            var task = Task.Factory.StartNew(() => DisplayNode(root),

                                            CancellationToken.None,

                                            TaskCreationOptions.None,

                                            TaskScheduler.Default);

            task.Wait();

        }

        static void DisplayNode(Node current)

        {

            if (current.Left != null)

                Task.Factory.StartNew(() => DisplayNode(current.Left),

                                            CancellationToken.None,

                                            TaskCreationOptions.AttachedToParent,

                                            TaskScheduler.Default);

            if (current.Right != null)

                Task.Factory.StartNew(() => DisplayNode(current.Right),

                                            CancellationToken.None,

                                            TaskCreationOptions.AttachedToParent,

                                            TaskScheduler.Default);

            Console.WriteLine("当前节点的值为{0};处理的ThreadId={1}", current.Text, Thread.CurrentThread.ManagedThreadId);

        }

    }

}

  

 2.3取消任务 CancellationTokenSource【没研究明白】

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        private static int TaskMethod(string name, int seconds, CancellationToken token)

        {

            Console.WriteLine("Task {0} is running on a thread id {1}. Is thread pool thread: {2}",

                name, Thread.CurrentThread.ManagedThreadId, Thread.CurrentThread.IsThreadPoolThread);

            for (int i = 0; i < seconds; i++)

            {

                Thread.Sleep(TimeSpan.FromSeconds(1));

                if (token.IsCancellationRequested) return -1;

            }

            return 42 * seconds;

        }

        private static void Main(string[] args)

        {

            var cts = new CancellationTokenSource();

            var longTask = new Task<int>(() => TaskMethod("Task 1", 10, cts.Token), cts.Token);

            Console.WriteLine(longTask.Status);

            cts.Cancel();

            Console.WriteLine(longTask.Status);

            Console.WriteLine("First task has been cancelled before execution");

            cts = new CancellationTokenSource();

            longTask = new Task<int>(() => TaskMethod("Task 2", 10, cts.Token), cts.Token);

            longTask.Start();

            for (int i = 0; i < 5; i++)

            {

                Thread.Sleep(TimeSpan.FromSeconds(0.5));

                Console.WriteLine(longTask.Status);

            }

            cts.Cancel();

            for (int i = 0; i < 5; i++)

            {

                Thread.Sleep(TimeSpan.FromSeconds(0.5));

                Console.WriteLine(longTask.Status);

            }

            Console.WriteLine("A task has been completed with result {0}.", longTask.Result);

        }

    }

}

2.4处理任务中的异常

  • 单个任务

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static int TaskMethod(string name, int seconds)

        {

            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(seconds));

            throw new Exception("Boom!");

            return 42 * seconds;

        }

        static void Main(string[] args)

        {

            try

            {

                Task<int> task = Task.Run(() => TaskMethod("Task 2", 2));

                int result = task.GetAwaiter().GetResult();

                Console.WriteLine("Result: {0}", result);

            }

            catch (Exception ex)

            {

                Console.WriteLine("Task 2 Exception caught: {0}", ex.Message);

            }

            Console.WriteLine("----------------------------------------------");

            Console.WriteLine();

        }

    }

}

  •  多个任务

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static int TaskMethod(string name, int seconds)

        {

            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(seconds));

            throw new Exception(string.Format("Task {0} Boom!", name));

            return 42 * seconds;

        }

        public static void Main(string[] args)

        {

            try

            {

                var t1 = new Task<int>(() => TaskMethod("Task 3", 3));

                var t2 = new Task<int>(() => TaskMethod("Task 4", 2));

                var complexTask = Task.WhenAll(t1, t2);

                var exceptionHandler = complexTask.ContinueWith(t =>

                        Console.WriteLine("Result: {0}", t.Result),

                        TaskContinuationOptions.OnlyOnFaulted

                    );

                t1.Start();

                t2.Start();

                Task.WaitAll(t1, t2);

            }

            catch (AggregateException ex)

            {

                ex.Handle(exception =>

                {

                    Console.WriteLine(exception.Message);

                    return true;

                });

            }

        }

    }

}

  •  async/await方式

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

using System;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static async Task ThrowNotImplementedExceptionAsync()

        {

            throw new NotImplementedException();

        }

        static async Task ThrowInvalidOperationExceptionAsync()

        {

            throw new InvalidOperationException();

        }

        static async Task Normal()

        {

            await Fun();

        }

        static Task Fun()

        {

            return Task.Run(() =>

            {

                for (int i = 1; i <= 10; i++)

                {

                    Console.WriteLine("i={0}", i);

                    Thread.Sleep(200);

                }

            });

        }

        static async Task ObserveOneExceptionAsync()

        {

            var task1 = ThrowNotImplementedExceptionAsync();

            var task2 = ThrowInvalidOperationExceptionAsync();

            var task3 = Normal();

            try

            {

                //异步的方式

                Task allTasks = Task.WhenAll(task1, task2, task3);

                await allTasks;

                //同步的方式

                //Task.WaitAll(task1, task2, task3);

            }

            catch (NotImplementedException ex)

            {

                Console.WriteLine("task1 任务报错!");

            }

            catch (InvalidOperationException ex)

            {

                Console.WriteLine("task2 任务报错!");

            }

            catch (Exception ex)

            {

                Console.WriteLine("任务报错!");

            }

        }

        public static void Main()

        {

            Task task = ObserveOneExceptionAsync();

            Console.WriteLine("主线程继续运行........");

            task.Wait();

        }

    }

}

  

 2.5Task.FromResult的应用

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

using System;

using System.Collections.Generic;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static IDictionary<stringstring> cache = new Dictionary<stringstring>()

        {

            {"0001","A"},

            {"0002","B"},

            {"0003","C"},

            {"0004","D"},

            {"0005","E"},

            {"0006","F"},

        };

        public static void Main()

        {

            Task<string> task = GetValueFromCache("0006");

            Console.WriteLine("主程序继续执行。。。。");

            string result = task.Result;

            Console.WriteLine("result={0}", result);

        }

        private static Task<string> GetValueFromCache(string key)

        {

            Console.WriteLine("GetValueFromCache开始执行。。。。");

            string result = string.Empty;

            //Task.Delay(5000);

            Thread.Sleep(5000);

            Console.WriteLine("GetValueFromCache继续执行。。。。");

            if (cache.TryGetValue(key, out result))

            {

                return Task.FromResult(result);

            }

            return Task.FromResult("");

        }

    }

}

  

2.6使用IProgress实现异步编程的进程通知【没有研究】

  • IProgress<in T>只提供了一个方法void Report(T value),通过Report方法把一个T类型的值报告给IProgress,然后IProgress<in T>的实现类Progress<in T>的构造函数接收类型为Action<T>的形参,通过这个委托让进度显示在UI界面中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

using System;

using System.Collections.Generic;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void DoProcessing(IProgress<int> progress)

        {

            for (int i = 0; i <= 100; ++i)

            {

                Thread.Sleep(100);

                if (progress != null)

                {

                    progress.Report(i);

                }

            }

        }

        static async Task Display()

        {

            //当前线程

            var progress = new Progress<int>(percent =>

            {

                Console.Clear();

                Console.Write("{0}%", percent);

            });

            //线程池线程

            await Task.Run(() => DoProcessing(progress));

            Console.WriteLine("");

            Console.WriteLine("结束");

        }

        public static void Main()

        {

            Task task = Display();

            task.Wait();

        }

    }

}

 

 2.7 C#Task返回值

Task返回值,目前有2种情况,一种是异步async返回值,一种是同步返回值

  • 第一种:异步返回值
    • Task方法如果加了async关键字,那么就是异步返回方法,如果是异步返回方法,需要返回一个值时,直接return value,就可以了。
  • 第二种:同步返回值
    • Task方法如果没有加async关键字,需要返回一个值时,使用Task.FromResult方法,Task.FromResult(value)就可以了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

using System;

using System.Collections.Generic;

using System.Threading;

using System.Threading.Tasks;

namespace ThreadStudy

{

    class Program

    {

        static void Main(string[] args)

        {

            Console.WriteLine(TaskMethod().Result);

            Console.WriteLine(TaskMethod2().Result);

        }

        async static Task<int> TaskMethod()

        {

            return 1;

        }

        static Task<int> TaskMethod2()

        {

            return Task.FromResult(2);

        }

    }

}

  

 

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C#中,async和await关键字用于实现异步编程。通过使用这两个关键字,我们可以在代码中处理异步操作,而不会阻塞主线程。async关键字用于标记一个方法为异步方法,而await关键字用于等待异步操作的完成。 在使用async和await的代码示例中,我们可以看到以下几个关键点: - 使用async关键字标记方法为异步方法,例如:private async void Test()。 - 使用await关键字等待异步操作的完成,例如:await Task.Delay(TimeSpan.FromSeconds(3))。 - 使用IAsyncEnumerable<T>接口来定义一个异步数据序列,例如:private async IAsyncEnumerable<int> ProduceAsyncSumSeqeunc(int count)。 - 使用foreach循环和await关键字来遍历异步数据序列,例如:await foreach (var value in sequence)。 需要注意的是,如果我们没有使用await关键字,那么方法将作为一个同步方法。编译器会显示警告,但不会报错。 总结起来,通过在C#代码中使用async和await关键字,我们可以轻松地进行异步编程,处理异步操作,提高程序的性能和响应能力。\[1\]\[2\]\[3\] #### 引用[.reference_title] - *1* *3* [C# 中的Async 和 Await 的用法详解](https://blog.csdn.net/weixin_41883890/article/details/126081451)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [C# async / await 用法](https://blog.csdn.net/qq_38693757/article/details/127867464)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值