.NET Worker Service 作为 Windows 服务运行及优雅退出改进

上一篇文章我们了解了如何为 Worker Service 添加 Serilog 日志记录,今天我接着介绍一下如何将 Worker Service 作为 Windows 服务运行。

我曾经在前面一篇文章的总结中提到过可以使用 sc.exe 实用工具将 Worker Service 安装为 Windows 服务运行,本文中我就来具体阐述一下如何实现它。

SC 是什么?

sc.exe 是包含于 Windows SDK 的,可用于控制服务的命令行实用程序,它的命令对应于服务控制管理器(SCM)[1] 提供的函数。

服务控制管理器(SCM) 是 Windows NT 系列操作系统中的一个特殊进程,它在操作系统启动时由 wininit 进程启动,用于启动和停止 Windows 进程(包括设备驱动程序和启动程序)。SCM 的主要功能是在系统启动时启动所有必需的服务,它类似于类 Unix 系统上的 init 进程(或者现代 Linux 发行版上使用的较新的 systemd init 系统),用于启动各种系统守护进程[2]。SCM 是一个远程过程调用(RPC)服务,服务配置和服务控制程序可以借它来控制远程计算机上的服务。

打开 Windows 命令提示符窗口,输入并运行 sc 命令,您便可以看到 sc.exe 实用工具的帮助信息:

> sc描述:SC 是用来与服务控制管理器和服务进行通信的命令行程序。
用法:sc <server> [command] [service name] <option1> <option2>...<server> 选项的格式为 "\\ServerName"可通过键入以下命令获取有关命令的更多帮助: "sc [command]"命令:query-----------查询服务的状态,或枚举服务类型的状态。queryex---------查询服务的扩展状态,或枚举服务类型的状态。start-----------启动服务。pause-----------向服务发送 PAUSE 控制请求。interrogate-----向服务发送 INTERROGATE 控制请求。continue--------向服务发送 CONTINUE 控制请求。stop------------向服务发送 STOP 请求。config----------更改服务的配置(永久)。description-----更改服务的描述。failure---------更改失败时服务执行的操作。failureflag-----更改服务的失败操作标志。sidtype---------更改服务的服务 SID 类型。privs-----------更改服务的所需特权。managedaccount--更改服务以将服务帐户密码标记为由 LSA 管理。qc--------------查询服务的配置信息。qdescription----查询服务的描述。qfailure--------查询失败时服务执行的操作。qfailureflag----查询服务的失败操作标志。qsidtype--------查询服务的服务 SID 类型。qprivs----------查询服务的所需特权。qtriggerinfo----查询服务的触发器参数。qpreferrednode--查询服务的首选 NUMA 节点。qmanagedaccount-查询服务是否将帐户与 LSA 管理的密码结合使用。qprotection-----查询服务的进程保护级别。quserservice----查询用户服务模板的本地实例。delete ----------(从注册表中)删除服务。create----------创建服务(并将其添加到注册表中)。control---------向服务发送控制。sdshow----------显示服务的安全描述符。sdset-----------设置服务的安全描述符。showsid---------显示与任意名称对应的服务 SID 字符串。triggerinfo-----配置服务的触发器参数。preferrednode---设置服务的首选 NUMA 节点。GetDisplayName--获取服务的 DisplayName。GetKeyName------获取服务的 ServiceKeyName。EnumDepend------枚举服务依赖关系。
...

您可以从帮助信息中看到 sc 实用工具支持的所有命令集及其介绍。我们在本文中要用到的命令有:

  • create----------创建服务(并将其添加到注册表中)

  • description-----更改服务的描述。

  • start-----------启动服务。

  • stop------------向服务发送 STOP 请求。

  • delete ----------(从注册表中)删除服务。

创建项目并发布

§下载 Worker Service 源码

我将基于上一篇文章中的 Worker Service 源码[3]来修改,如果您安装有 git,可以用下面的命令获取它:

git clone git@github.com:ITTranslate/WorkerServiceWithSerilog.git

然后,使用 Visual Studio Code 打开此项目,运行一下,以确保一切正常:

dotnet build
dotnet run

§添加 Windows Services 依赖

为了作为 Windows 服务运行,我们需要我们的 Worker 监听来自 ServiceBase 的启动和停止信号,ServiceBase 是将 Windows 服务系统公开给 .NET 应用程序的 .NET 类型。为此,我们需要添加 Microsoft.Extensions.Hosting.WindowsServices NuGet 包:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

然后修改 Program.cs 中的 CreateHostBuilder 方法,添加 UseWindowsService 方法调用:

public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args).UseWindowsService() // Sets the host lifetime to WindowsServiceLifetime....ConfigureServices((hostContext, services) =>{services.AddHostedService<Worker>();}).UseSerilog(); //将 Serilog 设置为日志提供程序

然后,运行一下构建命令,确保一切正常:

dotnet build

不出意外,您会看到 已成功生成 的提示。

§发布程序

运行 dotnet publish 命令将应用程序及其依赖项发布到文件夹(我的操作系统是 win10 x64 系统)[4]

dotnet publish -c Release -r win-x64 -o c:\test\workerpub

命令运行完成后,您会在 C:\test\workerpub 文件夹中看到可执行程序及其所有依赖项。

创建并运行服务

首先,需要特别注意的是:当我们使用 sc.exe 实用工具管理服务时,必须以管理员身份运行 Windows 命令提示符,否则会执行失败。

§安装服务

安装服务我们需要用到创建服务命令 —— sc create

以管理员身份打开 Windows 命令提示符窗口,输入并运行 sc create 命令,可以看到此命令的的帮助信息:

> sc create描述:在注册表和服务数据库中创建服务项。
用法:sc <server> create [service name] [binPath= ] <option1> <option2>...选项:
注意: 选项名称包括等号。等号和值之间需要一个空格。type= <own|share|interact|kernel|filesys|rec|userown|usershare>(默认 = own)start= <boot|system|auto|demand|disabled|delayed-auto>(默认 = demand)error= <normal|severe|critical|ignore>(默认 = normal)binPath= <.exe 文件的 BinaryPathName>group= <LoadOrderGroup>tag= <yes|no>depend= <依存关系(以 / (斜杠)分隔)>obj= <AccountName|ObjectName>(默认= LocalSystem)DisplayName= <显示名称>password= <密码>

命令 sc create 的参数说明[5]

  • server:指定服务所在的远程服务器的名称。名称必须使用通用命名约定(UNC)格式 (例如,\myserver) 。若要在本地运行 SC.exe,请不要使用此参数。

  • service name:指定 getkeyname 操作返回的服务名称。

  • binPath=:指定服务二进制文件的路径。binPath= 没有默认值,必须提供此字符串。

  • displayname= "显示名称":指定一个友好名称,用于标识用户界面程序中的服务。

  • start= {boot|system|auto|demand|disabled|delayed-auto}:指定服务的启动类型。选项包括:

    • boot - 指定由启动加载程序加载的设备驱动程序。

    • system - 指定在内核初始化过程中启动的设备驱动程序。

    • auto - 指定一项服务,该服务在计算机每次重新启动时自动启动并运行(即使没有人登录到计算机)。

    • demand - 指定必须手动启动的服务。如果未指定 start= ,则此值为默认值。

    • disabled - 指定无法启动的服务。若要启动已禁用的服务,请将启动类型更改为其他某个值。

    • delayed-auto - 指定一项服务,该服务将在启动其他自动服务之后的短时间自动启动。

注意事项:
1、每个命令行选项 (参数) 必须包含等号作为选项名称的一部分。
2、选项与其值之间必须有一个空格(例如,type= own),如果遗漏了空格,操作将失败。

了解了 sc create 命令的用法,不难得出此处我们所需要的命令如下:

sc create MyService binPath= "C:\test\workerpub\MyService.exe" start= auto displayname= "技术译站的测试服务"

运行以上命令,输出以下结果:

[SC] CreateService 成功

运行 services.msc 命令打开本地服务列表,可以看到我们的服务已经安装好了,服务名称显示为 技术译站的测试服务。它没有描述,处于已停止状态。

§设置服务的描述

输入并运行 sc description 命令,可以看到此命令的的帮助信息:

> sc description
描述:设置服务的描述字符串。
用法:sc <server> description [service name] [description]

运行以下命令给该服务添加描述信息:

sc description MyService "这是一个由 Worker Service 实现的测试服务。"

输出结果:

[SC] ChangeServiceConfig2 成功

运行成功以后,按 F5 刷新服务列表,您将看到服务描述已经更新了。

§启动服务

输入并运行 sc start 命令,可以看到此命令的的帮助信息:

> sc start描述:启动服务运行。
用法:sc <server> start [service name] <arg1> <arg2> ...

输入以下命令启动服务:

sc start MyService

输出结果:

[SC] StartService 失败 1053:服务没有及时响应启动或控制请求。

启动失败了,为什么呢?查看一下 Windows 事件查看器 --> 应用程序,显示的错误原因大致如下:

The process was terminated due to an unhandled exception.
Exception Info: System.IO.FileNotFoundException: The configuration file 'appsettings.json' was not found and is not optional. 
The physical path is 'C:\WINDOWS\system32\appsettings.json'.

回头看一下 Program.cs 文件,在 Main 方法中我们为配置设置的基路径是 Directory.GetCurrentDirectory()。但是作为 Windows Service 运行时,默认的当前工作目录是 C:\WINDOWS\system32,所以导致了这样的错误。为了解决这一问题,我们需要在设置配置的基路径前添加一行 Directory.SetCurrentDirectory(AppContext.BaseDirectory),代码如下:

// 作为 Windows Service 运行时,默认的当前工作目录是 C:\WINDOWS\system32,会导致找不到配置文件,
// 所以需要添加下面一行,指定当前工作目录为应用程序所在的实际目录。
Directory.SetCurrentDirectory(AppContext.BaseDirectory);var configuration = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "Production"}.json", true).Build();

作为 Windows Service 运行时,默认情况下,Directory.GetCurrentDirectory() 为 C:\WINDOWS\system32
AppDomain.CurrentDomain.BaseDirectory 和 AppContext.BaseDirectory 为应用程序所在的实际目录。
因为在有的依赖程序包中有用到 Directory.GetCurrentDirectory() 获取来程序所在目录,所以这里必须使用 Directory.SetCurrentDirectory 设置当前工作目录。

再次启动服务:

> sc start MyServiceSERVICE_NAME: MyServiceTYPE               : 10  WIN32_OWN_PROCESSSTATE              : 2  START_PENDING(NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)WIN32_EXIT_CODE    : 0  (0x0)SERVICE_EXIT_CODE  : 0  (0x0)CHECKPOINT         : 0x0WAIT_HINT          : 0x7d0PID                : 21736FLAGS              :

这次服务启动成功了。

§停止服务

运行以下命令,停止 MyService 服务。

sc stop MyService

输出结果:

SERVICE_NAME: MyServiceTYPE               : 10  WIN32_OWN_PROCESSSTATE              : 3  STOP_PENDING(STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)WIN32_EXIT_CODE    : 0  (0x0)SERVICE_EXIT_CODE  : 0  (0x0)CHECKPOINT         : 0x0WAIT_HINT          : 0x0

§删除服务

运行以下命令,(从注册表中)删除 MyService 服务。

sc delete MyService

输出结果:

[SC] DeleteService 成功

至此,我们使用 sc 实用工具演示了服务的创建、更改描述、启动、停止和删除。当服务创建完成以后,您也可以使用 Windows 服务管理器来维护服务的启动、停止等。

Windows Service 优雅退出

§问题

我查看了一下 C:\test\workerpub\Logs 目录下的日志信息,发现当停止服务的时候,它并没有像我将 Worker Service 作为控制台应用运行时那样优雅退出(等待关闭前必须完成的任务正常结束后再退出)。也就是说,我在.NET Worker Service 如何优雅退出[6]中使用的方法,在将 Worker Service 作为 Windows 服务运行时失效了。

这是什么原因呢,该如何解决呢?

§查找原因

我们来看一下 UseWindowsService 方法的源代码:

其中有这样一行:

// https://github.com/dotnet/runtime/blob/main/src/libraries/Microsoft.Extensions.Hosting.WindowsServices/src/WindowsServiceLifetimeHostBuilderExtensions.csservices.AddSingleton<IHostLifetime, WindowsServiceLifetime>();

也就是说,当 Worker Service 作为 Windows Service 运行时,使用的宿主(Host)生命周期控制类不再是作为控制台应用运行时的 ConsoleLifetime,而是 WindowsServiceLifetime,它派生自 ServiceBase

让我们来看一下 WindowsServiceLifetime 的源代码:

您会发现 WindowsServiceLifetime 类的 OnStop 和 OnShutdown 方法中调用了 ApplicationLifetime.StopApplication();而它的基类 ServiceBase 中,当服务停止时调用了 OnStop 和 OnShutdown 方法。也就是说,在 Windows 服务停止的时候已经调用了 ApplicationLifetime.StopApplication()。这就是我们在 Worker 中手动调用 StopApplication 失效的原因。

问题的原因找到了,该怎么解决它呢?

§解决方法

功夫不负有心人,在认真查阅了 dotnet runtime[7] 中 BackgroundService 、WindowsServiceLifetime 和 ApplicationLifetime 类的源代码后,终于找到了解决方法。既然 WindowsServiceLifetime 中调用了 StopApplication,那我就换别的方法呗。

注意到 ApplicationLifetime 的属性 ApplicationStopping(类型为 CancellationToken),它的注释是:

Triggered when the application host is performing a graceful shutdown. Request may still be in flight. Shutdown will block until this event completes.

所以,我们可以向它注册一个取消时调用的的委托操作。修改一下 Worker 类中的 StartAsync 方法,添加以下代码:

// 注册应用停止前需要完成的操作
_hostApplicationLifetime.ApplicationStopping.Register(() =>
{GetOffWork();
});

向 ApplicationStopping 注册的委托,在 StopAsync 之前运行。

修改后 Worker 类的完整代码如下:

public class Worker : BackgroundService
{/// <summary>/// 状态:0-默认状态,1-正在完成关闭前的必要工作,2-正在执行 StopAsync/// </summary>private volatile int _status = 0; //状态private readonly IHostApplicationLifetime _hostApplicationLifetime;private readonly ILogger<Worker> _logger;public Worker(IHostApplicationLifetime hostApplicationLifetime, ILogger<Worker> logger){_hostApplicationLifetime = hostApplicationLifetime;_logger = logger;}public override Task StartAsync(CancellationToken cancellationToken){// 注册应用停止前需要完成的操作_hostApplicationLifetime.ApplicationStopping.Register(() =>{GetOffWork();});_logger.LogInformation("上班了,又是精神抖擞的一天,output from StartAsync");return base.StartAsync(cancellationToken);}protected override async Task ExecuteAsync(CancellationToken stoppingToken){try{// 这里实现实际的业务逻辑while (!stoppingToken.IsCancellationRequested){try{_logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);await SomeMethodThatDoesTheWork(stoppingToken);}catch (Exception ex){_logger.LogError(ex, "Global exception occurred. Will resume in a moment.");}await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);}}finally{_logger.LogWarning("My worker service shut down.");}}private async Task SomeMethodThatDoesTheWork(CancellationToken cancellationToken){string msg = _status switch{1 => "正在完成关闭前的必要工作……",2 => "假装还在埋头苦干ing…… 其实我去洗杯子了",_ => "我爱工作,埋头苦干ing……"};_logger.LogInformation(msg);await Task.CompletedTask;}/// <summary>/// 关闭前需要完成的工作/// </summary>private void GetOffWork(){_status = 1;_logger.LogInformation("太好了,下班时间到,output from ApplicationStopping.Register Action at: {time}", DateTimeOffset.Now);           _logger.LogDebug("开始处理关闭前必须完成的工作 at: {time}", DateTimeOffset.Now);_logger.LogInformation("糟糕,有一个紧急 bug 需要下班前完成!!!");_logger.LogInformation("啊啊啊,我爱加班,我要再干 20 秒,Wait 1 ");Task.Delay(TimeSpan.FromSeconds(20)).Wait();_logger.LogInformation("啊啊啊啊啊啊,我爱加班,我要再干 1 分钟,Wait 2 ");Task.Delay(TimeSpan.FromMinutes(1)).Wait();_logger.LogInformation("啊哈哈哈哈哈,终于好了,可以下班了!");_logger.LogDebug("关闭前必须完成的工作处理完成 at: {time}", DateTimeOffset.Now);}public override Task StopAsync(CancellationToken cancellationToken){_status = 2;_logger.LogInformation("准备下班了,output from StopAsync at: {time}", DateTimeOffset.Now);_logger.LogInformation("去洗洗茶杯先……", DateTimeOffset.Now);Task.Delay(30_000).Wait();_logger.LogInformation("茶杯洗好了。", DateTimeOffset.Now);_logger.LogInformation("下班喽 ^_^", DateTimeOffset.Now);return base.StopAsync(cancellationToken);}
}

代码修改完成以后,停止服务,重新发布程序。

dotnet publish -c Release -r win-x64 -o c:\test\workerpub

再次启动服务然后关闭服务,您会发现,我们编写的 Windows Service 已经可以优雅退出了。

这种方法,不仅作为 Windows 服务运行时可以优雅退出,而且作为控制台应用运行时也一样适用,它比我在.NET Worker Service 如何优雅退出中介绍的方法更加完美。

总结

在本文中,我通过一个实例详细介绍了如何将 .NET Worker Service 作为 Windows 服务运行,并说明了如何使用 sc.exe 实用工具安装和管理服务。还改进了 Worker Service 优雅退出的方法,使它不仅适用于控制台应用而且适用于 Windows 服务。

当我们向 HostBuilder 添加了 .UseWindowsService() 方法调用后,编译出的程序,既可以作为控制台应用运行,也可以作为 Windows 服务运行。

您可以从 GitHub 下载本文中的源码[8]

相关阅读:

  • .NET Worker Service 入门介绍

  • .NET Worker Service 如何优雅退出

  • .NET Worker Service 添加 Serilog 日志记录


相关链接:

  1. https://docs.microsoft.com/zh-cn/windows/win32/services/service-control-manager ↩︎

  2. https://www.techopedia.com/definition/25522/service-control-manager-scm ↩︎

  3. https://github.com/ITTranslate/WorkerServiceWithSerilog ↩︎

  4. https://docs.microsoft.com/zh-cn/dotnet/core/tools/dotnet-publish ↩︎

  5. https://docs.microsoft.com/zh-cn/windows-server/administration/windows-commands/sc-create ↩︎

  6. https://mp.weixin.qq.com/s/voxAxh9rQQogE3_Yc1-eCQ ↩︎

  7. https://github.com/dotnet/runtime dotnet runtime ↩︎

  8. https://github.com/ITTranslate/WorkerServiceAsWindowsService 源码下载 ↩︎

作者 :技术译民
出品 :技术译站(https://ITTranslator.cn/)

END

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301592.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

私有云存储 linux,搭建nextcloud私有云存储网盘

本文将要为您介绍的是搭建nextcloud私有云存储网盘,具体完成步骤:简介&#xff1a;搭建个人云存储一般会想到ownCloud&#xff0c;堪称是自建云存储服务的经典。而Nextcloud是ownCloud原开发团队打造的号称是“下一代”存储.真正试用过后就由衷地赞同这个Nextcloud&#xff1a;…

我报了个税,隐私就被扒光了?

全世界只有3.14 % 的人关注了数据与算法之美1月14日&#xff0c;据外媒报道&#xff0c;美国参议院金融委员会正在向美国财政部和国税局施压&#xff0c;要求他们采取网络安全措施。相关数据显示&#xff0c;2015年美国约有700,000名纳税人身份信息遭到泄露&#xff0c;为了解决…

C# 外接(网口)双摄像头视频获取

【注意事项】------------------------------------1. 更新设备网络SDK时&#xff0c;SDK开发包【库文件】里的HCNetSDK.dll、HCCore.dll、PlayCtrl.dll、SuperRender.dll、AudioRender.dll、HCNetSDKCom文件夹、ssleay32.dll、libeay32.dll、hlog.dll、hpr.dll、zlib1.dll、lo…

Visual Studio 2022 Preview 1 和.NET 6 Preview 5 正式发布

具有里程碑意义的Visual Studio 2022 Preview 1正式发布&#xff0c;重点是64位&#xff0c;而没有增加新功能&#xff0c;并且同时也发布了.NET 6 Preview 5。https://devblogs.microsoft.com/visualstudio/visual-studio-2022-preview-1-now-available/https://devblogs.micr…

每日一笑 | 你知道你爸妈当年是怎么在一起的吗?

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图片来源于网络&#xff0c;侵权删&#xff09;

每日一笑 | 你知道程序媛最“大”的烦恼是什么吗?

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图片来源于网络&#xff0c;侵权删&#xff09;

什么?他居然想在DLL中放毒!

dotnet/runtime有一个issue[1]讨论了如何使用ModuleInitializer投放恶意代码&#xff0c;但是微软的开发经理表示不背这个锅&#xff01; 什么是ModuleInitializer&#xff1f;ModuleInitializerAttribute[2]是在.NET5.0中新增加的API。它的作用是应用于编译中的任意数量的静态…

为什么说人工智能是一个大谎言

全世界只有3.14 % 的人关注了数据与算法之美人工智能是一个很大的谎言。或者往轻了说&#xff0c;它是一个混淆概念。往重了说&#xff0c;是用来欺骗大众的流行语&#xff0c;并且流行度非常高。其实真正的内涵是“机器学习”。所以&#xff0c;真正强大的&#xff0c;每个人都…

NET问答: ThreadStatic 和 ThreadLocalT 哪一个更好 ?

咨询区 user2341923&#xff1a;[ThreadStatic] 被设计成特性&#xff0c; ThreadLocal<T> 被设计成泛型&#xff0c;为什么会有这两种设计方案呢 &#xff1f;谁能告诉我他们的优缺点各是什么&#xff1f;是不是泛型的方式更好一点&#xff1f;回答区 Sanjeev&#xff1…

指针变量的定义与引用

2019独角兽企业重金招聘Python工程师标准>>> void main() { int a 5,b3; int *p; p &a; b*P5; printf("%d\n", b); *p 4; printf("%d,%d\n",a,*p); } 输出结果&#xff1a;10 4 4 之前在学校的时候就没怎么搞懂&#xff0c; 现在…

Vim的配置

2019独角兽企业重金招聘Python工程师标准>>> set nocompatible source $VIMRUNTIME/vimrc_example.vim source $VIMRUNTIME/mswin.vim behave mswin set guifontCourier_New:h12cANSI colorscheme torte set nobackup set ts4 set lines33 columns100 set diffexprM…

WPF 命中测试HitTest

获取不规则图片的点击事件&#xff0c;如果一个Canvas中&#xff0c;有很多图形&#xff0c;比如下图&#xff1a;矩形&#xff0c;菱形等。如果每个图形都加一个点击事件&#xff0c;想要一个通用的方法&#xff0c;获取鼠标点击在了哪个图形上&#xff0c;这里可以使用Visual…

不足百元的乐高式积木,玩转数理机械原理

▲数据汪特别推荐点击上图进入玩酷屋作为一名资深积木达人&#xff0c;小木我可是大大小小的积木阅览无数&#xff0c;当然乐高也不会放过&#xff0c;虽然“钱包君”已经是路人了。&#xff08;每月的工资用来买乐高~&#xff09;之前给大家推荐了一款STEM积木&#xff0c;小小…

WPF 密码框水印与明文切换

WPF开发者QQ群&#xff1a; 340500857 欢迎转发、分享、点赞、在看&#xff0c;谢谢~。 效果预览&#xff08;更多效果请下载源码体验&#xff09;&#xff1a;一、PasswordWithPlainText.xaml 代码如下<UserControl x:Class"WpfPasswrod.CustomControls.PasswordWith…

麻省理工学院迷你猎豹机器人,四条腿都能后空翻了,又是黑科技啊~

全世界只有3.14 % 的人关注了数据与算法之美麻省理工学院公布了一款迷你猎豹机器人&#xff0c;是第一个做后空翻的四足机器人。只有20磅的四肢四足动物可弯曲并摆动腿部&#xff0c;使其能够向右或向上行走。机器人也可以在不平坦的地形上小跑&#xff0c;大约是普通人行走速度…

c语言宏高级用法,C语言宏高级用法 [总结]

1、前言今天看代码时候&#xff0c;遇到一些宏&#xff0c;之前没有见过&#xff0c;感觉挺新鲜。如是上网google一下&#xff0c;顺便总结一下&#xff0c;方便以后学习和运用。C语言程序中广泛的使用宏定义&#xff0c;采用关键字define进行定义&#xff0c;宏只是一种简单的…

Win 11 真的要来了!微软宣布 Win10 将于 2025 年终止支持!

微软今日正式更新支持页面&#xff0c;Windows 10 家庭版和专业版的支持将于 2025 年 10 月 14 日结束。值得注意的是&#xff0c;这是微软首次描述对 Windows 10 的支持结束&#xff0c;在此之前&#xff0c;微软仅记录了特定的 Windows 10 正式版本&#xff08;如最新的 Wind…

每日一笑 | 为什么椅子总是最乱的?

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图片来源于网络&#xff0c;侵权删&#xff09;

R,让你的数据分析更简便!

R语言被广泛应用于生物、医学、电商、新闻等数据相关行业&#xff0c;是目前主流数据应用软件之一。它是用来进行统计分析、绘图和统计编程的强大工具&#xff0c;它在大规模的数据挖掘、可视化和报告方面已经取得了巨大的成功。你能够轻松地获取各种的包&#xff08;通过 CRAN…

久坐伤腰!这款德国3D美臀坐垫,分散身体压力,保护腰椎尾椎。

▲数据汪特别推荐点击上图进入玩酷屋一直听闻德国是一个「直肠子」国家他们的严谨、认真、一丝不苟让这个只有8000万人口的国家竟有2300多个享誉全球的名牌小到牙刷牙线&#xff0c;大到汽车「德国制造」已成为高品质的代名词德国品质到底是一种怎样的存在&#xff1f;德国人生…