当前位置: 首页 > news >正文

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理

目录

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理

一、Task、async和 await 、Thread 基础概念

1、线程,多线程

2、Task

 3、async (await )

二、Task 的基础使用,Task 的创建 和 启动

1、创建并运行一个Task

2、创建有返回值的 Task

3、Task提供了 task.RunSynchronously()用于同步执行Task任务

三、Task的阻塞方法(Wait/WaitAll/WaitAny)

1、Thread 阻塞主线程方法 : thread.Join()方法

2、Task提供了 Wait/WaitAny/WaitAll 方法,可以更方便地控制线程阻塞

四、Task的延续操作(WhenAny/WhenAll/ContinueWith)

1、Task 的延续操作(WhenAny/WhenAll/ContinueWith)

 2、Task.Factory 的延续操作(WhenAny/WhenAll/ContinueWith)

五、Task 的任务取消(CancellationTokenSource)

1、Thread 的任务取消

 2、Task 的任务取消

3、 CancellationTokenSource  中 source.Token.Register 和 source.CancelAfter(XXXXms)

 五、异步方法(async/await)

六、Task、async和 await 、Thread 简单小结


一、Task、async和 await 、Thread 基础概念

1、线程,多线程

线程(Thread)是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。

在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的

CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。

多线程:可以同时完成多个任务;可以使程序的响应速度更快;可以让占用大量处理时间的任务或当前没有进行处理的任务定期将处理时间让给别的任务;可以随时停止任务;可以设置每个任务的优先级以优化程序性能。

class Program{static void Main(string[] args){//创建无参的线程Thread thread1 = new Thread(new ThreadStart(Thread1));//调用Start方法执行线程thread1.Start();Console.ReadKey();}/// <summary>/// 创建无参的方法/// </summary>static void Thread1(){Console.WriteLine("这是无参的方法");}}

2、Task

Task 是在ThreadPool的基础上推出的,我们简单了解下ThreadPool。

  • ThreadPool中有若干数量的线程,如果有任务需要处理时,会从线程池中获取一个空闲的线程来执行任务,任务执行完毕后线程不会销毁,而是被线程池回收以供后续任务使用。
  • 当线程池中所有的线程都在忙碌时,又有新任务要处理时,线程池才会新建一个线程来处理该任务
  • 如果线程数量达到设置的最大值,任务会排队,等待其他任务释放线程后再执行。
  • 线程池能减少线程的创建,节省开销,如下看一个ThreadPool的使用
 static void Main(string[] args){for (int i = 1; i <=10; i++){//ThreadPool执行任务ThreadPool.QueueUserWorkItem(new WaitCallback((obj) => {Console.WriteLine($"第{obj}个执行任务");}),i);}Console.ReadKey();
}

上边的代码通过ThreadPool执行了10个任务,执行结果为

 3、async (await )

async  异步主要用于修饰方法。

当一个方法被调用时,调用者需要等待该方法执行完毕并返回才能继续执行,我们称这个方法是同步方法;

当一个方法被调用时,立即返回,并获取一个线程执行该方法内部的业务,调用者不用等待该方法执行完毕,我们称这个方法为异步方法。

异步的好处在于非阻塞(调用线程不会暂停执行去等待子线程完成),因此我们把一些不需要立即使用结果、较耗时的任务设为异步执行,可以提高程序的运行效率。

C# 中的 async 和 await 关键字,异步编程非常流行。

1)当我们在处理 UI 时,在点击按钮时,我们使用了一个长时间运行的方法,比如读取一个大文件或其他需要很长时间的东西

2)在这种情况下,整个应用程序必须等待完成整个任务。

3)换句话说,如果同步应用程序中的任何进程被阻塞,整个应用程序都会被阻塞,我们的应用程序会停止响应,直到整个任务完成。

异步编程在这种情况下非常有用。通过使用异步编程,应用程序可以继续进行不依赖于整个任务完成的其他工作。

net4.0在ThreadPool的基础上推出了Task类,微软极力推荐使用Task来执行异步任务,现在C#类库中的异步方法基本都用到了Task;

net5.0推出了async/await,让异步编程更为方便。

二、Task 的基础使用,Task 的创建 和 启动

我们知道了ThreadPool的弊端:

(1)我们不能控制线程池中线程的执行顺序,

(2)也不能获取线程池内线程取消/异常/完成的通知。

net4.0在ThreadPool的基础上推出了Task

(1)Task拥有线程池的优点

(2)同时也解决了使用线程池不易控制的弊端

1、创建并运行一个Task

首先看一下怎么去创建并运行一个Task,Task的 创建 和 启动 执行的方式有如下三种:

  • Task task = new Task(); task.Start();
  • Task task = Task.Factory.StartNew();
  • Task task = Task.Run();
 		 static void Main(string[] args){//1.new方式实例化一个Task,需要通过Start方法启动Task task = new Task(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task1的线程ID为{Thread.CurrentThread.ManagedThreadId}");});task.Start();//2.Task.Factory.StartNew(Action action)创建和启动一个TaskTask task2 = Task.Factory.StartNew(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task2的线程ID为{ Thread.CurrentThread.ManagedThreadId}");});//3.Task.Run(Action action)将任务放在线程池队列,返回并启动一个TaskTask task3 = Task.Run(() =>{Thread.Sleep(100);Console.WriteLine($"hello, task3的线程ID为{ Thread.CurrentThread.ManagedThreadId}");});Console.WriteLine("执行主线程!");Console.ReadKey();}

运行结果如下,我们看到先打印"执行主线程",然后再打印各个任务,说明了Task不会阻塞主线程:

2、创建有返回值的 Task

上边的代码中 Task 都没有返回值,我们也可以创建有返回值的 Task,用法和没有返回值的基本一致,我们简单修改一下上边的栗子,代码如下:

 Task<数据类型>,task.Result 获取结果

值得注意的是 :task.Resut获取结果时会阻塞线程,即如果task没有执行完成,会等待task执行完成获取到Result,然后再执行后边的代码

  • Task<string> task = new Task(); task.Start();
  • Task<string> task = Task.Factory.StartNew();
  • Task<string> task = Task.Run();
         static void Main(string[] args){//1.new方式实例化一个Task,需要通过Start方法启动Task<string> task = new Task<string>(() =>{return $"hello, task1的ID为{Thread.CurrentThread.ManagedThreadId}";});task.Start();//2.Task.Factory.StartNew(Func func)创建和启动一个TaskTask<string> task2 =Task.Factory.StartNew<string>(() =>{return $"hello, task2的ID为{ Thread.CurrentThread.ManagedThreadId}";});//3.Task.Run(Func func)将任务放在线程池队列,返回并启动一个TaskTask<string> task3= Task.Run<string>(() =>{return $"hello, task3的ID为{ Thread.CurrentThread.ManagedThreadId}";});Console.WriteLine("执行主线程!");Console.WriteLine(task.Result);Console.WriteLine(task2.Result);Console.WriteLine(task3.Result);Console.ReadKey();}

运行结果如下:

3、Task提供了 task.RunSynchronously()用于同步执行Task任务

上边的所有代码中Task的执行都是异步的,不会阻塞主线程。有些场景下我们想让Task同步执行怎么办呢?Task提供了 task.RunSynchronously()用于同步执行Task任务

  • Task task = new Task(); task.RunSynchronously();
        static void Main(string[] args){Task task = new Task(() =>{Thread.Sleep(100);Console.WriteLine("执行Task结束!");});//同步执行,task会阻塞主线程task.RunSynchronously();Console.WriteLine("执行主线程结束!");Console.ReadKey();}

运行结果如下:

三、Task的阻塞方法(Wait/WaitAll/WaitAny)

1、Thread 阻塞主线程方法 : thread.Join()方法

Thread的 Join 方法 可以阻塞调用线程,但是有一些弊端:

如果我们要实现很多线程的阻塞时,每个线程都要调用一次Join方法;

如果我们想让所有的线程执行完毕(或者任一线程执行完毕)时,立即解除阻塞,使用Join方法不容易实现。

参考代码如下

          static void Main(string[] args){Thread th1 = new Thread(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});th1.Start();Thread th2 = new Thread(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});th2.Start();//阻塞主线程th1.Join();th2.Join();Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

运行结果如下:

2、Task提供了 Wait/WaitAny/WaitAll 方法,可以更方便地控制线程阻塞

  • Task.Wait() 表示等待task执行完毕,功能类似于thead.Join();
  • Task.WaitAll(Task[] tasks) 表示只有所有的task都执行完成了再解除阻塞;
  • Task.WaitAny(Task[] tasks)表示只要有一个task执行完毕就解除阻塞;

参考代码如下:

        static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//阻塞主线程。task1,task2都执行完毕再执行主线程//执行【task1.Wait();task2.Wait();】可以实现相同功能Task.WaitAll(new Task[] { task1, task2 });Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

运行结果如下

四、Task的延续操作(WhenAny/WhenAll/ContinueWith)

1、Task 的延续操作(WhenAny/WhenAll/ContinueWith)

上边的 Wait/WaitAny/WaitAll 方法返回值为 void,这些方法单纯的实现阻塞线程

我们现在想:让所有 task 执行完毕 (或者任一task执行完毕) 后,开始执行后续操作,怎么实现呢?

这时就可以用到WhenAny/WhenAll方法了,这些方法执行完成返回一个task实例。

  • task.WhenAll(Task[] tasks) 表示所有的task都执行完毕后再去执行后续的操作
  • task.WhenAny(Task[] tasks) 表示任一task执行完毕后就开始执行后续操作

参考代码如下:

        static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//task1,task2执行完了后执行后续操作Task.WhenAll(task1, task2).ContinueWith((t) => {Thread.Sleep(100);Console.WriteLine("执行后续操作完毕!");});Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

运行结果如下:

 2、Task.Factory 的延续操作(WhenAny/WhenAll/ContinueWith)

上边的栗子也可以通过 Task.Factory.ContinueWhenAll(Task[] tasks, Action continuationAction)和 Task.Factory.ContinueWhenAny(Task[] tasks, Action continuationAction) 来实现

修改上边代码如下

        static void Main(string[] args){Task task1 = new Task(() => {Thread.Sleep(500);Console.WriteLine("线程1执行完毕!");});task1.Start();Task task2 = new Task(() => {Thread.Sleep(1000);Console.WriteLine("线程2执行完毕!");});task2.Start();//通过TaskFactroy实现Task.Factory.ContinueWhenAll(new Task[] { task1, task2 }, (t) =>{Thread.Sleep(100);Console.WriteLine("执行后续操作");});Console.WriteLine("主线程执行完毕!");Console.ReadKey();}

运行结果不变,如下

五、Task 的任务取消(CancellationTokenSource)

1、Thread 的任务取消

在Task前我们执行任务采用的是Thread,Thread怎么取消任务呢?

一般流程是:

  • 1)设置一个变量来控制任务是否停止,如设置一个变量isStop
  • 2)然后线程轮询查看isStop,如果isStop为true就停止

参考代码如下:

        static void Main(string[] args){bool isStop = false;int index = 0;//开启一个线程执行任务Thread th1 = new Thread(() =>{while (!isStop){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});th1.Start();//五秒后取消任务执行Thread.Sleep(5000);isStop = true;Console.ReadKey();}

运行结果如下

 2、Task 的任务取消

Task中有一个专门的类 CancellationTokenSource 来取消任务执行

还是使用上边的例子,调整代码如下

        static void Main(string[] args){CancellationTokenSource source = new CancellationTokenSource();int index = 0;//开启一个task执行任务Task task1 = new Task(() =>{while (!source.IsCancellationRequested){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});task1.Start();//五秒后取消任务执行Thread.Sleep(5000);//source.Cancel()方法请求取消任务,IsCancellationRequested会变成truesource.Cancel();Console.ReadKey();}

程序运行的效果不变,如下

3、 CancellationTokenSource  中 source.Token.Register 和 source.CancelAfter(XXXXms)

CancellationTokenSource 的功能不仅仅是取消任务执行,我们可以使用

  • source.CancelAfter(5000)实现5秒后自动取消任务
  • source.Token.Register(Action action) 可以注册取消任务触发的回调函数,即任务被取消时注册的action会被执行

参考代码如下

        static void Main(string[] args){CancellationTokenSource source = new CancellationTokenSource();//注册任务取消的事件source.Token.Register(() =>{Console.WriteLine("任务被取消后执行xx操作!");});int index = 0;//开启一个task执行任务Task task1 = new Task(() =>{while (!source.IsCancellationRequested){Thread.Sleep(1000);Console.WriteLine($"第{++index}次执行,线程运行中...");}});task1.Start();//延时取消,效果等同于Thread.Sleep(5000);source.Cancel();source.CancelAfter(5000);Console.ReadKey();}

运行结果如下

 五、异步方法(async/await)

在 C# 5.0 中出现的 async 和 await ,让异步编程变得更简单。

此方法利用了 .NET Framework 4.5 及更高版本、.NET Core 和 Windows 运行时中的异步支持。

编译器可执行开发人员曾进行的高难度工作,且应用程序保留了一个类似于同步代码的逻辑结构。

  • async 用在方法定义前面,await只能写在带有async标记的方法中。
  • 注意await异步等待的地方,await后面的代码和前面的代码执行的线程可能不一样
  • async关键字创建了一个状态机,类似yield return 语句;await会解除当前线程的阻塞,完成其他任务

这里实现同步异步读取文件内容的方式。

我们可以看到异步读取代码和同步读取代码基本一致。async/await让异步编码变得更简单,我们可以像写同步代码一样去写异步代码。

注意一个小问题:异步方法中方法签名返回值为Task,代码中的返回值为T。

代码中GetContentAsync的签名返回值为Task,而代码中返回值为string。牢记这一细节对我们分析异步代码很有帮助。

异步方法签名的返回值有以下三种:

  • Task:如果调用方法想通过调用异步方法获取一个T类型的返回值,那么签名必须为Task;
  • Task:如果调用方法不想通过异步方法获取一个值,仅仅想追踪异步方法的执行状态,那么我们可以设置异步方法签名的返回值为Task;
  • void:如果调用方法仅仅只是调用一下异步方法,不和异步方法做其他交互,我们可以设置异步方法签名的返回值为void,这种形式也叫做“调用并忘记”。

参考代码如下

        static void Main(string[] args){//Console.WriteLine(" Environment.CurrentDirectory "+ Environment.CurrentDirectory);string content = GetContentAsync(Environment.CurrentDirectory + @"/test.txt").Result;//调用同步方法//string content = GetContent(Environment.CurrentDirectory + @"/test.txt");Console.WriteLine(content);Console.ReadKey();}/// <summary>/// 异步读取文件内容/// </summary>/// <param name="filename"></param>/// <returns></returns>async static Task<string> GetContentAsync(string filename){FileStream fs = new FileStream(filename, FileMode.Open);var bytes = new byte[fs.Length];//ReadAync方法异步读取内容,不阻塞线程Console.WriteLine("开始读取文件");int len = await fs.ReadAsync(bytes, 0, bytes.Length);string result = Encoding.UTF8.GetString(bytes);return result;}/// <summary>/// 同步读取文件内容/// </summary>/// <param name="filename"></param>/// <returns></returns>static string GetContent(string filename){FileStream fs = new FileStream(filename, FileMode.Open);var bytes = new byte[fs.Length];//Read方法同步读取内容,阻塞线程int len = fs.Read(bytes, 0, bytes.Length);string result = Encoding.UTF8.GetString(bytes);return result;}

运行结果如下:

六、Task、async和 await 、Thread 简单小结

最后我们简单记住一些特性,就可以有个较好的理解他们了:

  • 1)async/await是基于Task的
  • 2)而Task是对ThreadPool的封装改进,主要是为了更有效的控制线程池中的线程(ThreadPool中的线程,我们很难通过代码控制其执行顺序,任务延续和取消等等);
  • 3)ThreadPool基于Thread的,主要目的是减少Thread创建数量和管理Thread的成本。
  • 4)async/await Task是C#中更先进的,也是微软大力推广的特性
  • 5)我们在开发中可以尝试使用Task来替代Thread/ThreadPool,处理本地IO和网络IO任务是尽量使用async/await来提高任务执行效率。

相关文章:

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理

Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理 目录 Unity C# 之 Task、async和 await 、Thread 基础使用的Task的简单整理 一、Task、async和 await 、Thread 基础概念 1、线程&#xff0c;多线程 2、Task 3、async &#xff08;await &#xff09;…...

介绍 Docker 的基本概念和优势,以及在应用程序开发中的实际应用。

Docker是一个开放源代码的容器化平台&#xff0c;可以将应用程序及其依赖项打包到一个轻量级的容器中&#xff0c;以便在任何地方运行。以下是Docker的基本概念和优势&#xff1a; 基本概念&#xff1a; 镜像&#xff08;image&#xff09;&#xff1a;Docker的基本构建块&am…...

如何提取视频的音频到手机?这个音频提取方法很简单

提取视频中的音频可以帮助您获得视频的声音部分&#xff0c;而无需观看整个视频。这对于那些只想听视频的声音或想将视频的声音与其他音频内容混合使用的人来说非常方便。此外&#xff0c;提取音频也可以为需要创建音频剪辑或混音的音频制作者提供帮助。那么怎么提取呢&#xf…...

【算法刷题之哈希表(2)】

目录 1.leetcode-454. 四数相加 II2.leetcode-383. 赎金信&#xff08;1&#xff09;暴力解法&#xff08;2&#xff09;哈希法 3.leetcode-205. 同构字符串&#xff08;1&#xff09;哈希法&#xff08;2&#xff09;直接对比查找 4.leetcode-128. 最长连续序列5.总结 1.leetc…...

如何创建和销售在线健身业务

快速轻松地创建您自己的线上健身网站&#xff01; 越来越多的人在家健身&#xff0c;在线健身业务也随之快速增长。 虽然这个生意很红火&#xff0c;但是真的像看起来那么容易上手吗&#xff1f; 有了MemberPress&#xff0c;确实如此&#xff01; 在这篇文章中&#xff0c…...

使用IIC进行多数据读取测试

IIC系列文章: (1)I2C 接口控制器理论讲解 (2)I2C接口控制设计与实现 (3)I2C连续读写实现 (4)使用IIC进行多数据读取测试 文章目录 前言一、control_RD_req模块二、顶层文件(IIC_control_EEPROM)三、测试文件(control_RD_req_tb)前言 使用已完成的IIC模块,将256个数据写入…...

drools8尝试(加单元测试)

drools8的maven模板项目里没有单元测试, 相比而言drools7有个非常好的test senorios 那就自己弄一个 文件是.http后缀的,写了个简单的例子如下 //测试交通违章 POST http://localhost:8080/Traffic Violation accept: application/json Content-Type: application/json{&q…...

Web3和去中心化:互联网的下一个演化阶段

文章目录 Web3和去中心化的定义Web3&#xff1a;去中心化&#xff1a; 为什么Web3和去中心化如此重要&#xff1f;数据隐私和安全&#xff1a;去中心化的创新&#xff1a;去除中间商&#xff1a; Web3和去中心化的应用领域去中心化金融&#xff08;DeFi&#xff09;&#xff1a…...

stm32 之20.HC-06蓝牙模块

原理图显示使用usart3串口使用的是PB10和PB11引脚 直接配置usart3串口协议 void usart3_init(uint32_t baud) {GPIO_InitTypeDef GPIO_InitStructureure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//端口B硬件时钟打开RCC_AHB1PeriphClockC…...

[技术杂谈]macOS上todesk无法远程操作鼠标键盘

远程到被控Mac后能看到画面&#xff0c;鼠标键盘操作无反应 远程后发现画面显示正常&#xff0c;但是键盘和鼠标的操作没有响应 可能是辅助功能没有勾选ToDesk_Session的权限。 可按以下步骤操作&#xff1a; 1> 在左上角点击苹果图标&#xff0c;选择“系统偏好设置” …...

【C++设计模式】用简单工厂模式实现按汽车重量输出汽车类型

2023年8月24日&#xff0c;周四凌晨 #include<iostream>class CarType{ public:virtual std::string getType()0; };class MiniCar:public CarType{ public:std::string getType() override{return "小型车";}; };class MidSizeCar:public CarType{ public:std…...

【Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN】

Ubuntu20.04安装Nvidia驱动、CUDA和CUDNN 1 Nvidia驱动安装1.1 安装1.2 安装Nvidia可能会遇到的问题1.2.1 NVIDIA 驱动与 Nouveau 驱动不兼容1.2.2 ERROR: Unable to find the development tool cc 2 CUDA安装2.1 下载和安装2.2 配置CUDA环境 3 安装CUDNN4 切换CUDA版本 1 Nvid…...

[Python进阶] 类的设计模式

4.11 设计模式 在Python中&#xff0c;类的设计模式是指一种通用的解决方案或设计模板&#xff0c;针对特定的问题或需求构建类结构&#xff0c;并提供相关的方法和属性。这些设计模式可以帮助开发人员遵循最佳实践、提高代码质量、增强可读性、降低维护成本。 需要注意的是&a…...

设计模式 07 桥接模式

桥接模式&#xff08;Bridge Pattern&#xff09;属于结构型模式 概述 桥接模式是将抽象部分与它的实现部分分离&#xff0c;使它们都可以独立地变化。它是一种对象结构型模式&#xff0c;又称为柄体&#xff08;Handle and Body&#xff09;模式或接口&#xff08;Interface&…...

linux系统(centos、ubuntu、银河麒麟服务、uos、deepin)判断程序是否已安装,通用判断方法:使用所有应用和命令的判断

前言 项目中需要判断linux服务器中是否已经安装了某个服务 方法有很多种&#xff0c;但是很多都不通用&#xff0c; 脚本代码就不容易做成统一的 解决方案 用下面的脚本代码去进行判断 用jdk测试 脚本意思如下&#xff1a; 输入java -version命令&#xff0c;将返回的字…...

机器学习各算法优缺点汇总

链接: (链接: link)...

手把手教你部署Jenkins教程,小白也能学会(多图预警)!

背景 公司的前端、后端构建及部署工作都是人工去做&#xff0c;随着业务扩大&#xff0c;项目迭代速度变快&#xff0c;人员增多&#xff0c;各种问题都暴露出来&#xff0c;将通过一个简单案例分享一下基于Jenkins的前后端自动化工作流搭建的过程&#xff0c;搭建完这套工作流…...

一种IDEA疑难杂症的解决办法

解决办法 重启IDEA 针对于IDEA各种羡慕解析&#xff0c;运行时问题&#xff0c;但是无法通过搜索引擎得到答案的问题请试试此方法。 删除根目录下[.idea]文件夹后重启 此文件夹为idea首次导入项目时根据项目情况自动生成的配置文件。方便idea下次更快的解析项目。但是某些情…...

TikTok小店玩法有哪些?一起来玩转TiKTok!

随着TikTok在海外市场份额不断增加&#xff0c;越来越多卖家选择入驻TikTok跨境小店&#xff0c;但是在入驻之后我们需要知道有哪些主流玩法&#xff0c;才能根据实际情况制定合适的运营方案。接下来小编就给大家介绍一下TikTok小店玩法有哪些&#xff01; 本土模式 这种模式…...

Mongodb 集合插入文档自动生成ObjectId

插入单个文档 Mongodb 使用以下几种方法来插入文档 &#xff0c; Mongodb V5.0 使用 mongosh 客户端&#xff1a; 插入单个文档 db.collection.insertOne() 将单个 文档插入到集合中。 如果该集合当前不存在&#xff0c;则插入操作将创建该集合。 如果文档未指定_id字段&am…...

19c补丁后oracle属主变化,导致不能识别磁盘组

补丁后服务器重启&#xff0c;数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后&#xff0c;存在与用户组权限相关的问题。具体表现为&#xff0c;Oracle 实例的运行用户&#xff08;oracle&#xff09;和集…...

DockerHub与私有镜像仓库在容器化中的应用与管理

哈喽&#xff0c;大家好&#xff0c;我是左手python&#xff01; Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库&#xff0c;用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...

页面渲染流程与性能优化

页面渲染流程与性能优化详解&#xff08;完整版&#xff09; 一、现代浏览器渲染流程&#xff08;详细说明&#xff09; 1. 构建DOM树 浏览器接收到HTML文档后&#xff0c;会逐步解析并构建DOM&#xff08;Document Object Model&#xff09;树。具体过程如下&#xff1a; (…...

鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序

一、开发环境准备 ​​工具安装​​&#xff1a; 下载安装DevEco Studio 4.0&#xff08;支持HarmonyOS 5&#xff09;配置HarmonyOS SDK 5.0确保Node.js版本≥14 ​​项目初始化​​&#xff1a; ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...

Spring Boot面试题精选汇总

&#x1f91f;致敬读者 &#x1f7e9;感谢阅读&#x1f7e6;笑口常开&#x1f7ea;生日快乐⬛早点睡觉 &#x1f4d8;博主相关 &#x1f7e7;博主信息&#x1f7e8;博客首页&#x1f7eb;专栏推荐&#x1f7e5;活动信息 文章目录 Spring Boot面试题精选汇总⚙️ **一、核心概…...

C++.OpenGL (10/64)基础光照(Basic Lighting)

基础光照(Basic Lighting) 冯氏光照模型(Phong Lighting Model) #mermaid-svg-GLdskXwWINxNGHso {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-GLdskXwWINxNGHso .error-icon{fill:#552222;}#mermaid-svg-GLd…...

Rapidio门铃消息FIFO溢出机制

关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系&#xff0c;以下是深入解析&#xff1a; 门铃FIFO溢出的本质 在RapidIO系统中&#xff0c;门铃消息FIFO是硬件控制器内部的缓冲区&#xff0c;用于临时存储接收到的门铃消息&#xff08;Doorbell Message&#xff09;。…...

什么是Ansible Jinja2

理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具&#xff0c;可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板&#xff0c;允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板&#xff0c;并通…...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

MySQL JOIN 表过多的优化思路

当 MySQL 查询涉及大量表 JOIN 时&#xff0c;性能会显著下降。以下是优化思路和简易实现方法&#xff1a; 一、核心优化思路 减少 JOIN 数量 数据冗余&#xff1a;添加必要的冗余字段&#xff08;如订单表直接存储用户名&#xff09;合并表&#xff1a;将频繁关联的小表合并成…...