C#Task学习
简介:
Task 对象是一种的中心思想基于任务的异步模式首次引入.NET Framework 4 中。 因为由执行工作Task对象通常以异步方式执行线程池线程上而不是以同步方式在主应用程序线程中,可以使用Status属性,并将IsCanceled, IsCompleted,和IsFaulted属性,以确定任务的状态。
一.Task的创建
1.创建Task类
(1)
Task task = new Task(() =>
{
Console.WriteLine("hello world!");
});
task.Start();
(2)
new Task(() =>
{
Console.WriteLine("hello world!");
}).Start();
(3)带参数
new Task(x =>
{
Console.WriteLine(x.ToString());
}, "hello world!").Start();
(4)带返回值
Task<string> t = new Task<string>(x =>
{
return x.ToString();
}, "hello world!");
t.Start();
Console.WriteLine(t.Result);
2.Task.Factory.StartNew
(1)
Task.Factory.StartNew(() =>
{
Console.WriteLine("hello world!");
});
(2)带参数
Task.Factory.StartNew(x =>
{
Console.WriteLine(x.ToString());
}, "hello world!");
(3)带返回值
Task<string> t = Task.Factory.StartNew<string>(() =>
{
return "hello world!";
});
Console.WriteLine(t.Result);
3.Task.Run
Task.Run(() =>
{
Console.WriteLine("hello world!");
});
4.TaskStatus
Created = 0, //该任务已初始化,但尚未被计划。
WaitingForActivation = 1, //该任务正在等待 .NET Framework 基础结构在内部将其激活并进行计划。
WaitingToRun = 2,//该任务已被计划执行,但尚未开始执行。
Running = 3, //该任务正在运行,但尚未完成。
WaitingForChildrenToComplete = 4,//该任务已完成执行,正在隐式等待附加的子任务完成。
RanToCompletion = 5,//已成功完成执行的任务。
Canceled = 6, //该任务已通过对其自身的 CancellationToken 引发 OperationCanceledException 对取消进行了确认,此时该标记处于已发送信号状态;或者在该任务开始执行之前,已向该任务的CancellationToken 发出了信号。 有关详细信息,请参阅任务取消。
Faulted = 7 //由于未处理异常的原因而完成的任务。
二. TaskCreationOptions
Task.Factory.StartNew和创建Task类可以带TaskCreationOptions参数而Task.Run不可以带
//
// 摘要:
// 指定应使用默认行为。
None = 0,
//
// 摘要:
// 提示 System.Threading.Tasks.TaskScheduler 以一种尽可能公平的方式安排任务,这意味着较早安排的任务将更可能较早运行,而较晚安排运行的任务将更可能较晚运行。
PreferFairness = 1,
//
// 摘要:
// 指定任务将是长时间运行的、粗粒度的操作,涉及比细化的系统更少、更大的组件。 它会向 System.Threading.Tasks.TaskScheduler
// 提示,过度订阅可能是合理的。 可以通过过度订阅创建比可用硬件线程数更多的线程。 它还将提示任务计划程序:该任务需要附加线程,以使任务不阻塞本地线程池队列中其他线程或工作项的向前推动。
LongRunning = 2,
//
// 摘要:
// 指定将任务附加到任务层次结构中的某个父级。 默认情况下,子任务(即由外部任务创建的内部任务)将独立于其父任务执行。 可以使用 System.Threading.Tasks.TaskContinuationOptions.AttachedToParent
// 选项以便将父任务和子任务同步。 请注意,如果使用 System.Threading.Tasks.TaskCreationOptions.DenyChildAttach
// 选项配置父任务,则子任务中的 System.Threading.Tasks.TaskCreationOptions.AttachedToParent 选项不起作用,并且子任务将作为分离的子任务执行。
// 有关详细信息,请参阅附加和分离的子任务。
AttachedToParent = 4,
//
// 摘要:
// 指定任何尝试作为附加的子任务执行(即,使用 System.Threading.Tasks.TaskCreationOptions.AttachedToParent
// 选项创建)的子任务都无法附加到父任务,会改成作为分离的子任务执行。 有关详细信息,请参阅附加和分离的子任务。
DenyChildAttach = 8,
//
// 摘要:
// 防止环境计划程序被视为已创建任务的当前计划程序。 这意味着像 StartNew 或 ContinueWith 创建任务的执行操作将被视为 System.Threading.Tasks.TaskScheduler.Default
// 当前计划程序。
HideScheduler = 16
1. LongRunning
任务是长时间任务,就需要用LongRunning,可能会创建一个非线程池线程来执行该任务,防止阻塞线程池队列中的其他线程
private static void fun8()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine($"task1.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
}); Task.Factory.StartNew(() =>
{
Console.WriteLine($"task2.线程 id {Thread.CurrentThread.ManagedThreadId}. 是否为线程池线程: {Thread.CurrentThread.IsThreadPoolThread}");
}, TaskCreationOptions.LongRunning);
}
运行结果:

2. 父子任务(AttachedToParent,DenyChildAttach)
AttachedToParent:将子任务附加到父任务上,表现为:附加到父任务上的所有子任务都结束,父任务才结束
DenyChildAttach:不允许子任务附加到父任务上
(1)子任务不附加到父任务
private static void fun5()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
});
});
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

(2)子任务附加到父任务上,使用AttachedToParent
private static void fun6()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
},TaskCreationOptions.AttachedToParent);
});
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

(3)拒绝子任务附加到父任务上,使用DenyChildAttach
private static void fun7()
{
Task t = Task.Factory.StartNew(() =>
{
Console.WriteLine("parent"); Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("child");
}, TaskCreationOptions.AttachedToParent);
}, TaskCreationOptions.DenyChildAttach);
t.ContinueWith(x =>
{
Console.WriteLine("parent over");
});
}
运行结果:

三.CancellationToken 取消任务
private static void fun4()
{
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken Token = cancellationTokenSource.Token;
//结束任务回调
Token.Register(() =>
{
Console.WriteLine("canceled");
}); Task.Factory.StartNew(() =>
{
try
{
while (true)
{
Console.WriteLine("hello world!");
Thread.Sleep(100);
Token.ThrowIfCancellationRequested();
}
}
catch (OperationCanceledException ocex)
{
}
catch (ObjectDisposedException odex)
{
}
catch (Exception ex)
{
} }, Token); Thread.Sleep(1000); cancellationTokenSource.Cancel();
}
执行结果:

当执行cancellationTokenSource.Cancel()后,任务进行到Token.ThrowIfCancellationRequested()代码后,throw出OperationCanceledException异常,才结束任务并执行cancel回调
四.方法
| Wait | 等待 System.Threading.Tasks.Task 完成执行过程 |
| WaitAll | 等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程 |
| WaitAny | 等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程 |
| WhenAll | 创建一个任务,该任务将在所有 System.Threading.Tasks.Task 对象都完成时完成 |
| WhenAny | 任何提供的任务已完成时,创建将完成的任务 |
| ContinueWith | 创建一个在目标 System.Threading.Tasks.Task 完成时异步执行的延续任务 |
1 Wait
阻塞当前线程,等待任务执行完成
//等待 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
public bool Wait(int millisecondsTimeout);
//等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前取消标记已取消,等待将终止。
public void Wait(CancellationToken cancellationToken);
//等待 System.Threading.Tasks.Task 完成执行过程。 如果在任务完成之前超时间隔结束或取消标记已取消,等待将终止。
public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken);
//等待 System.Threading.Tasks.Task 完成执行过程。
public void Wait();
//等待 System.Threading.Tasks.Task 在指定的时间间隔内完成执行。
public bool Wait(TimeSpan timeout);
使用方式:
t.Wait();//无限等待
t.Wait(100);//等待100ms
t.Wait(TimeSpan.FromMilliseconds(1500));//等待1500ms
例:
private static void fun9()
{
Task t = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
Console.WriteLine("task");
});
t.Wait();
Console.WriteLine("main");
}
运行结果:

2.WaitAll
阻塞当前线程,等待所有任务执行完成
//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程。
public static void WaitAll(params Task[] tasks);
//等待所有提供的可取消 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static bool WaitAll(Task[] tasks, TimeSpan timeout);
//等待所有提供的 System.Threading.Tasks.Task 在指定的毫秒数内完成执行。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout);
//等待提供的所有 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static void WaitAll(Task[] tasks, CancellationToken cancellationToken);
//等待提供的所有 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消等待。
public static bool WaitAll(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
使用:
private static void fun10()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); //Task.WaitAll(t1, t2);
//Task.WaitAll(new Task[] { t1, t2 }, 200);
Task.WaitAll(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
Console.WriteLine("main");
}
运行结果:

3.WaitAny
阻塞当前线程,等待任一任务执行完成
//等待提供的任一 System.Threading.Tasks.Task 对象完成执行过程。
public static int WaitAny(params Task[] tasks);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的时间间隔内完成执行。
public static int WaitAny(Task[] tasks, TimeSpan timeout);
//等待任何提供的 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行。
public static int WaitAny(Task[] tasks, int millisecondsTimeout);
//等待提供的任何 System.Threading.Tasks.Task 对象完成执行过程(除非取消等待)。
public static int WaitAny(Task[] tasks, CancellationToken cancellationToken);
//等待提供的任何 System.Threading.Tasks.Task 对象在指定的毫秒数内完成执行,或等到取消标记取消。
public static int WaitAny(Task[] tasks, int millisecondsTimeout, CancellationToken cancellationToken);
使用:
private static void fun11()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); //Task.WaitAny(t1, t2);
//Task.WaitAny(new Task[] { t1, t2 }, 200);
Task.WaitAny(new Task[] { t1, t2 }, TimeSpan.FromMilliseconds(200));
Console.WriteLine("main");
}
结果:

4.WhenAll
不阻塞当前线程,等待所有任务执行完成后,可以进行ContinueWith
//创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task 对象都完成时完成。
public static Task WhenAll(IEnumerable<Task> tasks);
//创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task 对象都完成时完成。
public static Task WhenAll(params Task[] tasks);
//创建一个任务,该任务将在可枚举集合中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks);
//创建一个任务,该任务将在数组中的所有 System.Threading.Tasks.Task`1 对象都完成时完成。
public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
使用:
(1)不带返回值
public static Task WhenAll(params Task[] tasks);
private static void fun12()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); Task.WhenAll(t1, t2).ContinueWith(t =>
{
Console.WriteLine("WhenAll");
}); Console.WriteLine("main");
}
执行结果:

(2)带返回值
public static Task<TResult[]> WhenAll<TResult>(params Task<TResult>[] tasks);
private static void fun13()
{
Task<string> t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
return "task2";
}); Task.WhenAll(new Task<string>[] { t1, t2 }).ContinueWith(t =>
{
string s = "WhenAll:";
foreach (string item in t.Result)
{
s += item;
}
Console.WriteLine(s);
}); Console.WriteLine("main");
}
执行结果:

5.WhenAny
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(params Task[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task> WhenAny(IEnumerable<Task> tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
//任何提供的任务已完成时,创建将完成的任务。
public static Task<Task<TResult>> WhenAny<TResult>(IEnumerable<Task<TResult>> tasks);
使用:
(1)不带参数
public static Task<Task> WhenAny(params Task[] tasks);
private static void fun14()
{
Task t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
}); Task t2 = Task.Factory.StartNew(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
}); Task.WhenAny(t1, t2).ContinueWith(t =>
{
Console.WriteLine("WhenAny1");
}); Console.WriteLine("main");
}
运行结果:

(2)带参数:
public static Task<Task<TResult>> WhenAny<TResult>(params Task<TResult>[] tasks);
private static void fun15()
{
Task<string> t1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(100);
Console.WriteLine("task1");
return "response task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(150);
Console.WriteLine("task2");
return "response task2";
}); Task.WhenAny(new Task<string>[] { t1, t2 }).ContinueWith(t =>
{
t.Result.ContinueWith(tt =>
{
Console.WriteLine(tt.Result);
});
}); Console.WriteLine("main");
}
运行结果:

6.ContinueWith
相当于回调
6.1使用
(1)使用lambda表达式方式
private static void fun1()
{
Console.WriteLine("start");
Task<string> task1 = new Task<string>(() =>
{
Console.WriteLine("task0");
return "task1";
});
Task<string> task2 = task1.ContinueWith(t =>
{
Console.WriteLine(t.Result);
return "task2";
});
task1.Start();
Console.WriteLine("end");
Console.ReadKey();
}
2.使用函数
private static void fun2()
{
Console.WriteLine("start");
Task<string> task1 = new Task<string>(doTask1);
Task<string> task2 = task1.ContinueWith(doTask2);
task1.Start();
Console.WriteLine("end");
Console.ReadKey();
} private static string doTask1()
{
Console.WriteLine("task0");
return "task1";
} private static string doTask2(Task<string> t)
{
Console.WriteLine(t.Result);
return "task2";
}
运行结果:

6.2 ContineWith和task可能不在同一线程上
例:
private static void fun16()
{
Task.Factory.StartNew(() =>
{
Console.WriteLine($"task {Thread.CurrentThread.ManagedThreadId}");
}).ContinueWith(t =>
{
Console.WriteLine($"ContinueWith {Thread.CurrentThread.ManagedThreadId}");
});
}
运行结果:

七.TaskFactory类
方法:
| StartNew | 创建并启动任务 |
| ContinueWhenAll | 创建一个延续任务,该任务在一组指定的任务完成后开始 |
| ContinueWhenAny | 创建一个延续 System.Threading.Tasks.Task,它将在提供的组中的任何任务完成后马上开始 |
| FromAsync | 创建一个 System.Threading.Tasks.Task`1,表示符合异步编程模型模式的成对的开始和结束方法 |
1.ContinueWhenAll
相当于回调
效果其实和WhenAll差不多,只不过ContineWhenAll采用了回调的方式
使用:带返回值
private static void fun17()
{
Task<string> t1 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task2");
return "task2";
}); Task.Factory.ContinueWhenAll(new Task[] { t1, t2 }, t =>
{
string s = "ContinueWhenAll:";
foreach (Task<string> item in t)
{
s += item.Result;
}
Console.WriteLine(s);
});
}
运行结果:

2.ContinueWhenAny
相当于回调
效果其实和WhenAny差不多,只不过ContineWhenAny采用了回调的方式
使用:
private static void fun18()
{
Task<string> t1 = Task.Factory.StartNew<string>(() =>
{
Thread.Sleep(10);
Console.WriteLine("task1");
return "task1";
}); Task<string> t2 = Task.Factory.StartNew<string>(() =>
{
Console.WriteLine("task2");
return "task2";
}); Task.Factory.ContinueWhenAny(new Task[] { t1, t2 }, t =>
{
Console.WriteLine($"{(t as Task<string>).Result} 先执行完");
});
}
执行结果:

3.FromAsync
相当于异步委托的精简写法,其中ContinueWith相当于异步委托中的callback
使用:
public Task<TResult> FromAsync<TArg1, TResult>(Func<TArg1, AsyncCallback, object, IAsyncResult> beginMethod, Func<IAsyncResult, TResult> endMethod, TArg1 arg1, object state);
static void Main(string[] args)
{
fun20(); Console.WriteLine("Main");
Console.ReadKey();
} private static void fun20()
{
var func = new Func<string, string>(x =>
{
Thread.Sleep(1000);
Console.WriteLine(x);
return "callback";
}); Task.Factory.FromAsync(func.BeginInvoke, func.EndInvoke, "func", null).ContinueWith(t =>
{
Console.WriteLine(t.Result);
});
}
运行结果:

参考:
https://docs.microsoft.com/zh-cn/dotnet/api/system.threading.tasks.task?view=netframework-4.7.2
https://docs.microsoft.com/zh-cn/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap?view=netframework-4.7.2
https://www.cnblogs.com/leo_wl/archive/2012/03/03/2378695.html#_label2
C#Task学习的更多相关文章
- c# .Net并行和多线程编程之Task学习记录!
任务Task和线程Thread的区别: 1.任务是架构在线程之上的,也就是说任务最终还是要抛给线程去执行. 2.任务跟线程不是一对一的关系,比如开10个任务并不是说会开10个线程,这一点任务有点类似线 ...
- 微软BI 之SSIS 系列 - Execute SQL Task 中的 Single Row 与 Full Result Set 的处理技巧
开篇介绍 Execute SQL Task 这个控件在微软BI ETL 项目中使用的频率还是非常高的,也是大部分入门 SSIS 初学者最早接触到的几个控制流控件. 我们通常使用 Execute SQL ...
- [.net 多线程]Task
C# 异步编程Task整理(一) c# .Net并行和多线程编程之Task学习记录! .NET 实现并行的几种方式(一) Dispatcher介绍 [C#学习笔记]使用C#中的Dispatcher 用 ...
- 一个demo学会js
全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...
- js系列教程2-对象、构造函数、对象属性全解
全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...
- Siamese Neural Networks for One-shot Image Recognition
one-shot learning简介 这是迁移学习的两种极端形式 zero-shot learning 指的是我们之前没有这个类别的训练样本,但是我们可以学习到一个映射X->Y, 如果这个映射 ...
- Js基础知识2-对象、对象属性全解
Object对象 Object对象包含如下属性和方法,也就意味着一切对象(函数也是对象)都包含如下方法. 每种方法和属性在不同的对象中有不同的作用,并不是每种对象都有使用每个方法的必要. 下面是Obj ...
- Tokio,Rust异步编程实践之路
缘起 在许多编程语言里,我们都非常乐于去研究在这个语言中所使用的异步网络编程的框架,比如说Python的 Gevent.asyncio,Nginx 和 OpenResty,Go 等,今年年初我开始接触 ...
- C#线程和异步
C#Thread学习 C#ThreadPool学习 C#Task学习 C#backgroundWorker c# 锁的使用 C#前台线程和后台线程区别 C#Async,await异步简单介绍 C#委托 ...
随机推荐
- 安装android studio时候弹出unable to access android sdk add-on list解决方法
本文转载自:http://www.cnblogs.com/rancvl/p/6081791.html Android Studio First Run 检测 Android SDK 及更新,由于众所周 ...
- git 本地文件里内容 操作记录
本地环境文件合并分支(以下的都分别 commit提交了的) [一.分支[追加] 和 [新增] 新信息 合并主线 情景] 分支内容: dr.find_element_by_id("su&qu ...
- 【转】jmeter压力测试
jmeter压力测试 Apache JMeter是Apache组织开发的基于Java的压力测试工具.用于对软件做压力测试,它最初被设计用于Web应用测试但后来扩展到其他测试领域, 是压力测试的首选软件 ...
- java代码实现从键盘输入编号,输出价格,并且不再编号内的,无效输入!!!!
总结:请给我更好的建议 package com.badu; import java.util.Scanner; //从键盘输入次数,通过输入的编号,输出对应的的商品价格: public class t ...
- PHP面向对象深入研究之【了解类】与【反射API】
了解类 class_exists验证类是否存在 <?php // TaskRunner.php $classname = "Task"; $path = "task ...
- Java多线程总结【转】
多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程 ...
- JAVA的FileOutput/InputStream使用实例
在JAVA中,要读写文件,要使用Stream这个东西. Stream简单来说,可以看做在程序和文件之间打开了一个管道,然后把数据通过这个管道输送到文件或程序中去. FileOutput/InputSt ...
- java Web jsp四大作用域和九大内置对象
JSP中的四大作用域:page.request.session.application 这四大作用域,其实就是其九大内置对象中的四个,为什么说他们也是JSP的四大作用域呢?因为这四个对象都能存储数据, ...
- 哪些 IT 职位难以替代,竞争力强?
原文出自知乎:http://www.zhihu.com/question/24795311 有10多年的软件行业经验,只针对软件行业来回答这个问题: 很少有无法替代的职位,只能说替代的成本高低而已. ...
- SpringBoot20 集成SpringSecurity02 -> 利用SpringSecurity进行前后端分离的登录验证
1 SpirngBoot环境搭建 创建一个SpringBoot项目即可,详情参见三少的相关博文 参考博文 -> 点击前往 SpirngBoot项目脚手架 -> 点击前往 2 引入Spirn ...