解決ASP.NET Core在Task中使用IServiceProvider的問題

前言

????問題的起因是在幫同事解決遇到的一個問題,他的本意是在EF Core中為了解決避免多個線程使用同一個DbContext實例的問題。但是由于對Microsoft.Extensions.DependencyInjection體系的深度不是很了解,結果遇到了新的問題,當時整得我也有點蒙了,所以當時也沒解決,而且當時快下班了,就想著第二天再解決。在地鐵上,經過我一系列的思維跳躍,終于想到了問題的原因,第二天也順利的解決了這個問題。雖然我前面說了EFCore,但是本質和EFCore沒有關系,只是湊巧。解決了之后覺得這個問題是個易錯題,覺得挺有意思的,便趁機記錄一下。

問題演示

接下來我們還原一下當時的場景,以下代碼只是作為演示,無任何具體含義,只是為了讓操作顯得更清晰一下,接下來就貼一下當時的場景代碼

[Route("api/[controller]/[action]")]
[ApiController]
public?class?InformationController?:?ControllerBase
{private?readonly?LibraryContext?_libraryContext;private?readonly?IServiceProvider?_serviceProvider;private?readonly?ILogger<InformationController>?_logger;public?InformationController(LibraryContext?libraryContext,?IServiceProvider?serviceProvider,ILogger<InformationController>?logger){_libraryContext?=?libraryContext;_serviceProvider?=?serviceProvider;_logger?=?logger;}[HttpGet]public?string?GetFirst(){var?caseInfo?=?_libraryContext.Informations.Where(i?=>?i.IsDelete?==?0).FirstOrDefault();//這里直接使用了Task方式Task.Run(()?=>?{try{//Task里創建了新的IServiceScopeusing?var?scope?=?_serviceProvider.CreateScope();//通過IServiceScope創建具體實例LibraryContext?dbContext?=?scope.ServiceProvider.GetService<LibraryContext>();var?list?=?dbContext!.Informations.Where(i?=>?i.IsDelete?==?0).Take(100).ToList();}catch?(Exception?ex){_logger.LogError(ex.Message,?ex);}});return?caseInfo.Title;}
}

再次強調一下,上述代碼純粹是為了讓演示更清晰,無任何業務含義,不喜勿噴。咱們首先看一下這段代碼表現出來的意思,就是在ASP.NET Core的項目里,在Task.Run里使用IServiceProvider去創建Scope的場景。如果對ASP.NET Core Controller生命周期和IServiceProvider不夠了解的話,會很容易遇到這個問題,且不知道是什么原因。上述這段代碼會偶現一個錯誤

Cannot?access?a?disposed?object.
Object?name:?'IServiceProvider'.

這里為什么說是偶現呢?因為會不會出現異常完全取決于Task.Run里的代碼是在當前請求輸出之前執行完成還是之后完成。說到這里相信有一部分同學已經猜到了代碼報錯的原因了。問題的本質很簡單,是因為IServiceProvider被釋放掉了。我們知道默認情況下ASP.NET Core為每次請求處理會創建單獨的IServiceScope,這會關乎到聲明周期為Scope對象的聲明周期。所以如果Task.Run里的邏輯在請求輸出之前執行完成,那么代碼運行沒任何問題。如果是在請求完成之后完成再執行CreateScope操作,那必然會報錯。因為Task.Run里的邏輯何時被執行,這個是由系統CPU調度本身決定的,特別是CPU比較繁忙的時候,這種異常會變得更加頻繁。

這個問題不僅僅是在Task.Run這種場景里,類似的本質就是在一個IServiceScope里創建一個新的子Scope作用域的時候,這個時候需要注意的是父級的IServiceProvider釋放問題,如果父級的IServiceProvider已經被釋放了,那么基于這個Provider再去創建Scope則會出現異常。但是這個問題在結合Task或者多線程的時候,更容易出現問題。

解決問題

既然我們知道了它為何會出現異常,那么解決起來也就順理成章了。那就是保證當前請求執行完成之前,最好保證Task.Run里的邏輯也要執行完成,所以我們上述的代碼會變成這樣

[HttpGet]
public?async?Task<string>?GetFirst()
{var?caseInfo?=?_libraryContext.Informations.Where(i?=>?i.IsDelete?==?0).FirstOrDefault();//這里使用了await?Task方式await?Task.Run(()?=>?{try{//Task里創建了新的IServiceScopeusing?var?scope?=?_serviceProvider.CreateScope();//通過IServiceScope創建具體實例LibraryContext?dbContext?=?scope.ServiceProvider.GetService<LibraryContext>();var?list?=?dbContext!.Informations.Where(i?=>?i.IsDelete?==?0).Take(100).ToList();}catch?(Exception?ex){_logger.LogError(ex.Message,?ex);}});return?caseInfo.Title;
}

試一下,發現確實能解決問題,因為等待Task完成能保證Task里的邏輯能在請求執行完成之前完成。但是,很多時候我們并不需要等待Task執行完成,因為我們就是希望它在后臺線程去執行這些操作,而不需要阻塞執行。
上面我們提到了本質是解決在IServiceScope創建子Scope時遇到的問題,因為這里注入進來的IServiceProvider本身是Scope的,只在當前請求內有效,所以基于IServiceProvider去創建IServiceScope要考慮到當前IServiceProvider是否釋放。那么我們就得打破這個枷鎖,我們要想辦法在根容器中去創建新的IServiceScope。這一點我大微軟自然是考慮到了,在Microsoft.Extensions.DependencyInjection體系中提供了IServiceScopeFactory這個根容器的作用域,基于根容器創建的IServiceScope可以得到平行與當前請求作用域的獨立的作用域,而不受當前請求的影響。改造上面的代碼用以下形式

[Route("api/[controller]/[action]")]
[ApiController]
public?class?InformationController?:?ControllerBase
{private?readonly?LibraryContext?_libraryContext;private?readonly?IServiceScopeFactory?_scopeFactory;private?readonly?ILogger<InformationController>?_logger;public?InformationController(LibraryContext?libraryContext,?IServiceScopeFactory?scopeFactory,ILogger<InformationController>?logger){_libraryContext?=?libraryContext;_scopeFactory?=?scopeFactory;_logger?=?logger;}[HttpGet]public?string?GetFirst(){var?caseInfo?=?_libraryContext.Informations.Where(i?=>?i.IsDelete?==?0).FirstOrDefault();//這里直接使用了Task方式Task.Run(()?=>?{try{//Task里創建了新的IServiceScopeusing?var?scope?=?_scopeFactory.CreateScope();//通過IServiceScope創建具體實例LibraryContext?dbContext?=?scope.ServiceProvider.GetService<LibraryContext>();var?list?=?dbContext!.Informations.Where(i?=>?i.IsDelete?==?0).Take(100).ToList();}catch?(Exception?ex){_logger.LogError(ex.Message,?ex);}});return?caseInfo.Title;}
}

如果你是調試起來的話你可以看到IServiceScopeFactory的具體實例是Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope類型的,它里面包含了一個IsRootScope屬性,通過這個屬性我們可以知道當前容器作用域是否是根容器作用域。當使用IServiceProvider實例的時候IsRootScopefalse,當使用IServiceScopeFactory實例的時候IsRootScopetrue。使用CreateScope創建IServiceScope實例的時候,注意用完了需要釋放,否則可能會導致TransientScope類型的實例得不到釋放。在之前的文章咱們曾提到過TransientScope類型的實例都是在當前容器作用域釋放的時候釋放的,這個需要注意一下。

問題探究

上面我們了解到了在每次請求的時候使用IServiceProvider和使用IServiceScopeFactory的時候他們作用域的實例來源是不一樣的。IServiceScopeFactory來自根容器,IServiceProvider則是來自當前請求的Scope。順著這個思路我們可以看一下他們兩個究竟是如何的不相同。這個問題還得從構建Controller實例的時候,注入到Controller中的實例作用域的問題。

請求中的IServiceProvider

在之前的文章<ASP.NET Core Controller與IOC的羈絆>[1]我們知道,Controller是每次請求都會創建新的實例,我們再次拿出來這段核心的代碼來看一下,在DefaultControllerActivator類的Create方法中[點擊查看源碼👈[2]]

internal?class?DefaultControllerActivator?:?IControllerActivator
{private?readonly?ITypeActivatorCache?_typeActivatorCache;public?DefaultControllerActivator(ITypeActivatorCache?typeActivatorCache){_typeActivatorCache?=?typeActivatorCache;}public?object?Create(ControllerContext?controllerContext){//省略一系列判斷代碼var?serviceProvider?=?controllerContext.HttpContext.RequestServices;//這里傳遞的IServiceProvider本質就是來自HttpContext.RequestServicesreturn?_typeActivatorCache.CreateInstance<object>(serviceProvider,?controllerTypeInfo.AsType());}
}

通過這個方法我們可以看到創建Controller實例時,如果存在構造依賴,本質則是通過HttpContext.RequestServices實例創建出來的,而它本身就是IServiceProvider的實例,ITypeActivatorCache實例中則存在真正創建Controller實例的邏輯,具體可以查看TypeActivatorCache類的實現[點擊查看源碼👈[3]]

internal?class?TypeActivatorCache?:?ITypeActivatorCache
{private?readonly?Func<Type,?ObjectFactory>?_createFactory?=(type)?=>?ActivatorUtilities.CreateFactory(type,?Type.EmptyTypes);private?readonly?ConcurrentDictionary<Type,?ObjectFactory>?_typeActivatorCache?=new?ConcurrentDictionary<Type,?ObjectFactory>();public?TInstance?CreateInstance<TInstance>(IServiceProvider?serviceProvider,Type?implementationType){//省略一系列判斷代碼var?createFactory?=?_typeActivatorCache.GetOrAdd(implementationType,?_createFactory);//創建Controller的時候,需要的依賴實例都是來自IServiceProviderreturn?(TInstance)createFactory(serviceProvider,?arguments:?null);}
}

其實在這里我們就可以得到一個結論,我們在當前請求默認通過構造注入的IServiceProvider的實例其實就是HttpContext.RequestServices,也就是針對當前請求的作用域有效,同樣的是來自當前作用域的Scope周期的對象實例也是在當前請求結束就會釋放。驗證這個很簡單可以寫個demo來演示一下

[Route("api/[controller]/[action]")]
[ApiController]
public?class?InformationController?:?ControllerBase
{private?readonly?IServiceProvider?_serviceProvider;public?InformationController(IServiceProvider?serviceProvider){_serviceProvider?=?serviceProvider;}[HttpGet]public?bool[]?JudgeScope([FromServices]IServiceProvider?scopeProvider){//比較構造注入的和在HttpContext獲取的bool?isEqualOne?=?_serviceProvider?==?HttpContext.RequestServices;//比較通過Action綁定的和在HttpContext獲取的bool?isEqualTwo?=?scopeProvider?==?HttpContext.RequestServices;return?new[]?{?isEqualOne,?isEqualTwo?};}
}

毫無疑問,默認情況下isEqualOne和isEqualTwo的結構都是true,這也驗證了我們上面的結論。因此在當前請求默認注入IServiceProvider實例的時候,都是來自HttpContext.RequestServices的實例。

請求中的IServiceProvider和IServiceScopeFactory

上面我們看到了在當前請求中獲取IServiceProvider實例本身就是Scope的,而且在當前請求中通過各種注入方式獲取到的實例都是相同的。那么接下來我們就可以繼續跟蹤,本質的HttpContext.RequestServices的IServiceProvider到底來自什么地方呢?我們找到HttpContext默認的實現類DefaultHttpContext中關于RequestServices屬性的定義[點擊查看源碼👈[4]]

//接受
public?IServiceScopeFactory?ServiceScopeFactory?{?get;?set;?}?=?default!;
//數據來自RequestServicesFeature
private?static?readonly?Func<DefaultHttpContext,?IServiceProvidersFeature>?_newServiceProvidersFeature?=?context?=>?new?RequestServicesFeature(context,?context.ServiceScopeFactory);
//緩存來自_newServiceProvidersFeature
private?IServiceProvidersFeature?ServiceProvidersFeature?=>_features.Fetch(ref?_features.Cache.ServiceProviders,?this,?_newServiceProvidersFeature)!;
//數據來自ServiceProvidersFeature的RequestServices
public?override?IServiceProvider?RequestServices
{get?{?return?ServiceProvidersFeature.RequestServices;?}set?{?ServiceProvidersFeature.RequestServices?=?value;?}
}

通過上面的源碼我們可以看到HttpContext.RequestServices的數據最終來自RequestServicesFeature類的RequestServices屬性,我們可以直接找到RequestServicesFeature類的定義[點擊查看源碼👈[5]]

public?class?RequestServicesFeature?:?IServiceProvidersFeature,?IDisposable,?IAsyncDisposable
{private?readonly?IServiceScopeFactory??_scopeFactory;private?IServiceProvider??_requestServices;private?IServiceScope??_scope;private?bool?_requestServicesSet;private?readonly?HttpContext?_context;public?RequestServicesFeature(HttpContext?context,?IServiceScopeFactory??scopeFactory){_context?=?context;_scopeFactory?=?scopeFactory;}public?IServiceProvider?RequestServices{get{if?(!_requestServicesSet?&&?_scopeFactory?!=?null){//釋放掉之前沒釋放掉的RequestServicesFeature實例_context.Response.RegisterForDisposeAsync(this);//通過IServiceScopeFactory創建Scope_scope?=?_scopeFactory.CreateScope();//RequestServices來自IServiceScopeFactory的CreateScope實例_requestServices?=?_scope.ServiceProvider;//填充已經設置了RequestServices的標識_requestServicesSet?=?true;}return?_requestServices!;}set{_requestServices?=?value;_requestServicesSet?=?true;}}//釋放的真實邏輯public?ValueTask?DisposeAsync(){switch?(_scope){case?IAsyncDisposable?asyncDisposable:var?vt?=?asyncDisposable.DisposeAsync();if?(!vt.IsCompletedSuccessfully){return?Awaited(this,?vt);}vt.GetAwaiter().GetResult();break;case?IDisposable?disposable:disposable.Dispose();break;}//釋放時重置相關屬性_scope?=?null;_requestServices?=?null;return?default;static?async?ValueTask?Awaited(RequestServicesFeature?servicesFeature,?ValueTask?vt){await?vt;servicesFeature._scope?=?null;servicesFeature._requestServices?=?null;}}//IDisposable的Dispose的方法,通過using可隱式調用public?void?Dispose(){DisposeAsync().AsTask().GetAwaiter().GetResult();}
}

通過上面的兩段源碼,我們得到了許多關于IServiceProvider和IServiceScopeFactory的相關信息。

  • ??DefaultHttpContext的RequestServices值來自于RequestServicesFeature實例的RequestServices屬性

  • ??RequestServicesFeature的RequestServices屬性的值通過IServiceScopeFactory通過CreateScope創建的

  • ??構建RequestServicesFeature的IServiceScopeFactory值來自于DefaultHttpContext的ServiceScopeFactory屬性

那么接下來我們直接可以找到DefaultHttpContext的ServiceScopeFactory屬性是誰給它賦的值,我們找到創建HttpContext的地方,在DefaultHttpContextFactory的Create方法里[點擊查看源碼👈[6]]

public?class?DefaultHttpContextFactory?:?IHttpContextFactory
{private?readonly?IHttpContextAccessor??_httpContextAccessor;private?readonly?FormOptions?_formOptions;private?readonly?IServiceScopeFactory?_serviceScopeFactory;public?DefaultHttpContextFactory(IServiceProvider?serviceProvider){_httpContextAccessor?=?serviceProvider.GetService<IHttpContextAccessor>();_formOptions?=?serviceProvider.GetRequiredService<IOptions<FormOptions>>().Value;//通過IServiceProvider的GetRequiredService直接獲取IServiceScopeFactory_serviceScopeFactory?=?serviceProvider.GetRequiredService<IServiceScopeFactory>();}//創建HttpContext實例的方法public?HttpContext?Create(IFeatureCollection?featureCollection){if?(featureCollection?is?null){throw?new?ArgumentNullException(nameof(featureCollection));}var?httpContext?=?new?DefaultHttpContext(featureCollection);Initialize(httpContext);return?httpContext;}private?DefaultHttpContext?Initialize(DefaultHttpContext?httpContext){//IHttpContextAccessor也是在這里賦的值if?(_httpContextAccessor?!=?null){_httpContextAccessor.HttpContext?=?httpContext;}httpContext.FormOptions?=?_formOptions;//DefaultHttpContext的ServiceScopeFactory屬性值來自注入的IServiceProviderhttpContext.ServiceScopeFactory?=?_serviceScopeFactory;return?httpContext;}
}

這里我們可以看到IServiceScopeFactory的實例來自于通過DefaultHttpContextFactory注入的IServiceProvider實例,這里獲取IServiceScopeFactory的地方并沒有CreateScope,所以這里的IServiceScopeFactoryIServiceProvider中的實例都是來自根容器。這個我們還可以通過注冊DefaultHttpContextFactory地方看到 [點擊查看源碼👈[7]]

services.TryAddSingleton<IHttpContextFactory,?DefaultHttpContextFactory>();

通過這里可以看到DefaultHttpContextFactory注冊的是單例模式,注冊它的地方則是在IHostBuilderConfigureServices方法里。關于每次請求的創建流程,不是本文的重點,但是為了讓大家對本文講解的IServiceScopeFactoryIServiceProvider來源更清楚,咱們可以大致的描述一下

  • ??GenericWebHostService類實現自IHostedService,在StartAsync方法中啟動了IServer實例,默認則是啟動的Kestrel。

  • ??IServer啟動的方法StartAsync中會傳遞HostingApplication實例,構建HostingApplication實例的時候則會依賴IHttpContextFactory實例,而IHttpContextFactory實例則是在構建GenericWebHostService服務的時候注入進來的。

  • ??當每次請求ASP.NET Core服務的時候會調用HostingApplicationCreateContext方法,該方法中則會創建HttpContext實例,每次請求結束后則調用該類的DisposeContext釋放HttpContext實例。

說了這么多其實就是為了方便讓大家得到一個關系,即在每次請求中獲取的IServiceProvider實例來自HttpContext.RequestServices實例,HttpContext.RequestServices實例來自IServiceScopeFactory來自CreateScope方法創建的實例,而IServiceScopeFactory實例則是來自根容器,且DefaultHttpContextFactory的生命周期則和當前ASP.NET Core保持一致。

后續插曲

就在解決這個問題后不久,有一次不經意間翻閱微軟的官方文檔,發現官方文檔有提到相關的問題,而且也是結合efcore來講的。標題是《Do not capture services injected into the controllers on background threads》[8]翻譯成中文大概就是不要在后臺線程上捕獲注入控制器的服務,說的正是這個問題,微軟給我們的建議是

  • ??注入一個IServiceScopeFactory以便在后臺工作項中創建一個范圍。

  • ??IServiceScopeFactory是一個單例對象。

  • ??在后臺線程中創建一個新的依賴注入范圍。

  • ??不引用控制器中的任何東西。

  • ??不從傳入請求中捕獲DbContext。

得到的結論和我們在本文描述的基本上是差不多的,而且微軟也很貼心的給我們提供了相關示例

[HttpGet("/fire-and-forget-3")]
public?IActionResult?FireAndForget3([FromServices]IServiceScopeFactory?serviceScopeFactory)
{_?=?Task.Run(async?()?=>{await?Task.Delay(1000);using?(var?scope?=?serviceScopeFactory.CreateScope()){var?context?=?scope.ServiceProvider.GetRequiredService<ContosoDbContext>();context.Contoso.Add(new?Contoso());await?context.SaveChangesAsync();????????????????????????????????????????}});return?Accepted();
}

原來還是自己的坑自己最了解,也不得不說微軟現在的文檔確實挺詳細的,同時也提醒我們有空還是得多翻一翻文檔避免踩坑。

總結

????本文主要是通過幫助同事解決問題而得到的靈感,覺得挺有意思的,希望能幫助更多的人了解這個問題,且能避免這個問題。我們應該深刻理解ASP.NET Core處理每次請求則都會創建一個Scope,這會影響當前請求獲取的IServiceProvider實例,和通過IServiceProvider創建的生命周期為Scope的實例。如果把握不住,則可以理解為當前請求直接注入的服務,和當前服務直接注入的IServiceProvider實例。如果想獲取根容器的實例則可以通過獲取IServiceScopeFactory實例獲取,最后請注意IServiceScope的釋放問題。 ????曾幾何時,特別喜歡去解決遇到的問題,特別喜歡那種解決問題沉浸其中的過程。解決了問題,了解到為什么會讓自己感覺很通透,也更深刻,不經意間的也擴展了自己的認知邊界。這個過程得到的經驗是一種通識,是一種意識。而思維和意識則是我們適應這個不斷在變化時代的底層邏輯。

引用鏈接

[1]?<ASP.NET Core Controller與IOC的羈絆>:?https://www.cnblogs.com/wucy/p/14222973.html
[2]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Mvc/Mvc.Core/src/Controllers/DefaultControllerActivator.cs#L56
[3]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Mvc/Mvc.Core/src/Infrastructure/TypeActivatorCache.cs#L38
[4]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Http/Http/src/DefaultHttpContext.cs#L176
[5]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Http/Http/src/Features/RequestServicesFeature.cs#L33
[6]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Hosting/Hosting/src/Http/DefaultHttpContextFactory.cs#L45
[7]?點擊查看源碼👈:?https://github.com/dotnet/aspnetcore/blob/v6.0.7/src/Hosting/Hosting/src/GenericHost/GenericWebHostBuilder.cs#L93
[8]?《Do not capture services injected into the controllers on background threads》:?https://docs.microsoft.com/en-us/aspnet/core/performance/performance-best-practices?view=aspnetcore-6.0#do-not-capture-services-injected-into-the-controllers-on-background-threads

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/283844.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/283844.shtml
英文地址,請注明出處:http://en.pswp.cn/news/283844.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

什么是SRE?一文詳解SRE運維體系

在任何有一定規模的企業內部&#xff0c;一旦推行起來整個SRE的運維模式&#xff0c;那么對于可觀測性系統的建設將變得尤為重要&#xff0c;而在整個可觀測性系統中。 可觀測性系統 在任何有一定規模的企業內部&#xff0c;一旦推行起來整個SRE的運維模式&#xff0c;那么對于…

python初探

python近兩年似乎已經很熱了&#xff0c;不了解一下怎么能行呢&#xff0c;似乎python最大的優點就是簡潔、易懂、優雅。目前豆瓣、知乎等后臺服務使用的也都是python語言。 python一般可以用于網站服務、小工具、數據分析等工作。它作為高級語言&#xff0c;和js一樣&#xff…

Linux系統PATH變量配置

alias命令用于設置命令的別名&#xff0c;格式為“alias 別名命令” 例如擔心復制文件時誤將文件被覆蓋&#xff0c;可以執行alias cp" cp -i"&#xff0c;如此一來 每次復制命令都會詢問用戶是否要覆蓋。 unalias命令用于取消命令的別名&#xff1a;格式為"una…

solr5.5索引mysql數據(新手總結)

一 solr5.5環境部署到Eclipse(luna版&#xff09; solr部署參見&#xff1a;http://blog.csdn.net/csmnjk/article/details/64121765 二 Ik分詞器設置 IK分詞器設置參見:http://blog.csdn.net/csmnjk/article/details/51693578 solr4版本的schema.xml文件對應solr5版本的manage…

老板加薪!看我做的WPF Loading!!!

老板加薪&#xff01;看我做的WPF Loading&#xff01;&#xff01;&#xff01;控件名&#xff1a;RingLoading作者&#xff1a;WPFDevelopersOrg原文鏈接&#xff1a; https://github.com/WPFDevelopersOrg/WPFDevelopers.Minimal框架使用大于等于.NET40&#xff1b;Visua…

如何避免下重復訂單

電子交易的一個很基本的問題&#xff0c;就是避免用戶下重復訂單。用戶明明想買一次&#xff0c;結果一看下了兩個單。如果沒有及時發現&#xff0c;就會帶來額外的物流成本和扯皮。對商家的信譽也不好看。 從技術上看&#xff0c;這是一個分布式一致性問題&#xff1b;但實際…

圖像分類學習筆記

1.計算機認識圖像的方式&#xff1a;都是數字。例如一個 128X128 的3通道的圖片 是由 128X128X3個數字 組成的。 2.面臨的難點&#xff1a;一幅圖可以說明。 3.分類器 A&#xff1a;Nearest Neighbor Classifier&#xff1a;與CNN無關&#xff0c;但是可以幫助我們理解一下分類…

知物由學 | 干貨!一文了解安卓APP逆向分析與保護機制

“知物由學”是網易云易盾打造的一個品牌欄目&#xff0c;詞語出自漢王充《論衡實知》。人&#xff0c;能力有高下之分&#xff0c;學習才知道事物的道理&#xff0c;而后才有智慧&#xff0c;不去求問就不會知道。“知物由學”希望通過一篇篇技術干貨、趨勢解讀、人物思考和沉…

[轉]以終為始,詳細分析高考志愿該怎么填

為什么寫這篇文章&#xff1f; 之所以寫本文&#xff0c;是因為我自己有用處。 我簡要介紹&#xff0c;長話短說。我從一個普通的211本科畢業&#xff0c;已經接受社會"毒打"多年&#xff0c;回想起高考填志愿&#xff0c;依然會覺得有些許遺憾。我在貴州省的一個小縣…

ASP.NET Core 中的重定向

前言在《如何使用ASP.NET Core Web API實現短鏈接服務》中&#xff0c;我們使用了Redirect方法返回跳轉狀態碼:[HttpGet("{shortUrl}")] public IActionResult GetUrl(string shortUrl) {var hashids new Hashids("公眾號My IO", minHashLength: 6);var i…

C#IO

System.IO 命名空間包含允許讀寫文件和數據流的類型以及提供基本文件和目錄支持的類型。string url "C:\chisp.log";if (System.IO.File.Exists(url)){Response.Write("文件存在");}else{ Response.Write("文件不存在"); }System.IO.File.Exist…

Lind.DDD.Manager里的3,7,15,31,63,127,255,511,1023,2047

回到目錄 進制 我是一個程序猿&#xff0c;我喜歡簡單的數字&#xff0c;十進制如何&#xff0c;數字太多&#xff0c;有10種數字組成&#xff0c;但由于它廣為人知&#xff0c;所有使用最為廣泛&#xff0c;人們的慣性思維培養了十進制&#xff0c;并說它是最容易被計算的數字…

3.20學習內容,字符串與列表

一、字符串類型&#xff1a; 作用&#xff1a;名字&#xff0c;性別&#xff0c;國籍&#xff0c;地址等描述信息 定義&#xff1a;在單引號\雙引號\三引號內&#xff0c;由一串字符組成。 需要掌握的方法&#xff1a; 1、strip 去除指定字符lstrip 去除左邊指定字符rstri…

客戶端應用試用限制設計

1.概要最近接到公司安排的任務給客戶端設計一個“試用30天”的一個需求&#xff0c;其功能主要是為了防止客戶拿到產品之后不支付尾款繼續使用。眾所周知靠純軟件想防“盜版”&#xff0c;“限制試用”等做法是行業難題。只要價值足夠高一定有人會破解繞過你的所有防線達到免費…

【開發工具之Spring Tool Suite】6、用Spring Tool Suite簡化你的開發

如果你是一個喜歡用spring的人&#xff0c;你可能會在欣賞spring的強大功能外&#xff0c;對其各樣的配置比較郁悶&#xff0c;尤其是相差較大的版本在配置文件方面會存在差異&#xff0c;當然你可以去花不少的時間去網上查找相關的資料&#xff0c;當你準備使用更高版本spring…

康威定律,作為架構師還不會靈活運用?

Soft skills are always hard than hard skills. 軟技能比硬技能難。 老板聽說最近流行“微服務”&#xff0c;問架構師咱們的系統要不要來一套&#xff1f;老板又聽說最近流行“中臺系統”&#xff0c;問架構師咱們要不要搞起來&#xff1f;其實&#xff0c;這些問題不用老板問…

使用onclick跳轉到其他頁面。使用button跳轉到指定url

1. οnclick"javascript:window.location.hrefaa.htm" 2. οnclick"locationURL"3,。 οnclick"window.location.href?id11"轉載于:https://www.cnblogs.com/wujixing/p/5856087.html

Avalonia Beta 1對WPF做了很多改進

\看新聞很累&#xff1f;看技術新聞更累&#xff1f;試試下載InfoQ手機客戶端&#xff0c;每天上下班路上聽新聞&#xff0c;有趣還有料&#xff01;\\\Avalonia將自己定義為“基于WPF&#xff08;使用XAML、數據綁定以及lookless控件等&#xff09;的跨平臺.NET UI框架。”在第…

WebView2 通過 PuppeteerSharp 實現RPA獲取壁紙 (案例版)

此案例是《.Net WebView2 項目&#xff0c;實現 嵌入 WEB 頁面 Chromium內核》文的續集。主要是針對WebView2的一些微軟自己封裝的不熟悉的API&#xff0c;有一些人已經對 PuppeteerSharp很熟悉了&#xff0c;那么&#xff0c;直接用 PuppeteerSharp的話&#xff0c;那就降低了…

[轉]2022 年 Java 行業分析報告

你好&#xff0c;我是看山。 前段時間介紹了從 Java8 到 Java17 每個版本比較有特點的新特性&#xff08;收錄在 從小工到專家的 Java 進階之旅 專欄&#xff09;&#xff0c;今天看到 JRebel 發布了《2022 年 Java 發展趨勢和分析》&#xff0c;于是借此分析一下 Java 行業的現…