主頁 > .NET開發 > 在ASP.NET Core中創建基于Quartz.NET托管服務輕松實作作業調度

在ASP.NET Core中創建基于Quartz.NET托管服務輕松實作作業調度

2020-09-15 13:35:25 .NET開發

在這篇文章中,我將介紹如何使用ASP.NET Core托管服務運行Quartz.NET作業,這樣的好處是我們可以在應用程式啟動和停止時很方便的來控制我們的Job的運行狀態,接下來我將演示如何創建一個簡單的 IJob,一個自定義的 IJobFactory和一個在應用程式運行時就開始運行的QuartzHostedService,我還將介紹一些需要注意的問題,即在單例類中使用作用域服務,

作者:依樂祝

首發地址:https://www.cnblogs.com/yilezhu/p/12644208.html

參考英文地址:https://andrewlock.net/creating-a-quartz-net-hosted-service-with-asp-net-core/

簡介-什么是Quartz.NET?

在開始介紹什么是Quartz.NET前先看一下下面這個圖,這個圖基本概括了Quartz.NET的所有核心內容,

注:此圖為百度上獲取,旨在學習交流使用,如有侵權,聯系后洗掉,

Quartz.NET

以下來自他們的網站的描述:

Quartz.NET是功能齊全的開源作業調度系統,適用于從最小型的應用程式到大型企業系統,

對于許多ASP.NET開發人員來說它是首選,用作在計時器上以可靠、集群的方式運行后臺任務的方法,將Quartz.NET與ASP.NET Core一起使用也非常相似-因為Quartz.NET支持.NET Standard 2.0,因此您可以輕松地在應用程式中使用它,

Quartz.NET有兩個主要概念:

  • Job,這是您要按某個特定時間表運行的后臺任務,
  • Scheduler,這是負責基于觸發器,基于時間的計劃運行作業,

ASP.NET Core通過托管服務對運行“后臺任務”具有良好的支持,托管服務在ASP.NET Core應用程式啟動時啟動,并在應用程式生命周期內在后臺運行,通過創建Quartz.NET托管服務,您可以使用標準ASP.NET Core應用程式在后臺運行任務,

雖然可以創建“定時”后臺服務(例如,每10分鐘運行一次任務),但Quartz.NET提供了更為強大的解決方案,通過使用Cron觸發器,您可以確保任務僅在一天的特定時間(例如,凌晨2:30)運行,或僅在特定的幾天運行,或任意組合運行,它還允許您以集群方式運行應用程式的多個實體,以便在任何時候只能運行一個實體(高可用),

在本文中,我將介紹創建Quartz.NET作業的基本知識并將其調度為在托管服務中的計時器上運行,

安裝Quartz.NET

Quartz.NET是.NET Standard 2.0 NuGet軟體包,因此非常易于安裝在您的應用程式中,對于此測驗,我創建了一個ASP.NET Core專案并選擇了Empty模板,您可以使用dotnet add package Quartz來安裝Quartz.NET軟體包,這時候查看該專案的.csproj,應如下所示:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Quartz" Version="3.0.7" />
  </ItemGroup>

</Project>

創建一個IJob

對于我們正在安排的實際后臺作業,我們將通過向注入的ILogger<>中寫入“ hello world”來進行實作進而向控制臺輸出結果),您必須實作包含單個異步Execute()方法的Quartz介面IJob,請注意,這里我們使用依賴注入將日志記錄器注入到建構式中,

using Microsoft.Extensions.Logging;
using Quartz;
using System;
using System.Threading.Tasks;

namespace QuartzHostedService
{
    [DisallowConcurrentExecution]
    public class HelloWorldJob : IJob
    {
        private readonly ILogger<HelloWorldJob> _logger;

        public HelloWorldJob(ILogger<HelloWorldJob> logger)
        {
            _logger = logger ?? throw new ArgumentNullException(nameof(logger));
        }

        public Task Execute(IJobExecutionContext context)
        {
            _logger.LogInformation("Hello world by yilezhu at {0}!",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
            return Task.CompletedTask;
        }
    }
}

我還用[DisallowConcurrentExecution]屬性裝飾了該作業,該屬性可防止Quartz.NET嘗試同時運行同一作業,

創建一個IJobFactory

接下來,我們需要告訴Quartz如何創建IJob的實體,默認情況下,Quartz將使用Activator.CreateInstance創建作業實體,從而有效的呼叫new HelloWorldJob(),不幸的是,由于我們使用建構式注入,因此無法正常作業,相反,我們可以提供一個自定義的IJobFactory掛鉤到ASP.NET Core依賴項注入容器(IServiceProvider)中:

using Microsoft.Extensions.DependencyInjection;
using Quartz;
using Quartz.Spi;
using System;

namespace QuartzHostedService
{
    public class SingletonJobFactory : IJobFactory
    {
        private readonly IServiceProvider _serviceProvider;

        public SingletonJobFactory(IServiceProvider serviceProvider)
        {
            _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
        }

        public IJob NewJob(TriggerFiredBundle bundle, IScheduler scheduler)
        {
            return _serviceProvider.GetRequiredService(bundle.JobDetail.JobType) as IJob;
        }

        public void ReturnJob(IJob job)
        {
            
        }
    }
}

該工廠將一個IServiceProvider傳入建構式中,并實作IJobFactory介面,這里最重要的方法是NewJob()方法,在這個方法中工廠必須回傳Quartz調度程式所請求的IJob,在此實作中,我們直接委托給IServiceProvider,并讓DI容器找到所需的實體,由于GetRequiredService的非泛型版本回傳的是一個物件,因此我們必須在末尾將其強制轉換成IJob

ReturnJob方法是調度程式嘗試回傳(即銷毀)工廠創建的作業的地方,不幸的是,使用內置的IServiceProvider沒有這樣做的機制,我們無法創建適合Quartz API所需的新的IScopeService,因此我們只能創建單例作業,

這個很重要,使用上述實作,僅對創建單例(或瞬態)的IJob實作是安全的,

配置作業

我在IJob這里僅顯示一個實作,但是我們希望Quartz托管服務是適用于任何數量作業的通用實作,為了解決這個問題,我們創建了一個簡單的DTO JobSchedule,用于定義給定作業型別的計時器計劃:

using System;
using System.ComponentModel;

namespace QuartzHostedService
{
    /// <summary>
    /// Job調度中間物件
    /// </summary>
    public class JobSchedule
    {
        public JobSchedule(Type jobType, string cronExpression)
        {
            this.JobType = jobType ?? throw new ArgumentNullException(nameof(jobType));
            CronExpression = cronExpression ?? throw new ArgumentNullException(nameof(cronExpression));
        }
        /// <summary>
        /// Job型別
        /// </summary>
        public Type JobType { get; private set; }
        /// <summary>
        /// Cron運算式
        /// </summary>
        public string CronExpression { get; private set; }
        /// <summary>
        /// Job狀態
        /// </summary>
        public JobStatus JobStatu { get; set; } = JobStatus.Init;
    }

    /// <summary>
    /// Job運行狀態
    /// </summary>
    public enum JobStatus:byte
    {
        [Description("初始化")]
        Init=0,
        [Description("運行中")]
        Running=1,
        [Description("調度中")]
        Scheduling = 2,
        [Description("已停止")]
        Stopped = 3,

    }
}

這里的JobType是該作業的.NET型別(在我們的例子中就是HelloWorldJob),并且CronExpression是一個Quartz.NET的Cron表達,Cron運算式允許復雜的計時器調度,因此您可以設定下面復雜的規則,例如“每月5號和20號在上午8點至10點之間每半小時觸發一次”,只需確保檢查檔案即可,因為并非所有作業系統所使用的Cron運算式都是可以互換的,

我們將作業添加到DI并在Startup.ConfigureServices()中配置其時間表:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Impl;
using Quartz.Spi;

namespace QuartzHostedService
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            //添加Quartz服務
            services.AddSingleton<IJobFactory, SingletonJobFactory>();
            services.AddSingleton<ISchedulerFactory, StdSchedulerFactory>();
            //添加我們的Job
            services.AddSingleton<HelloWorldJob>();
            services.AddSingleton(
                 new JobSchedule(jobType: typeof(HelloWorldJob), cronExpression: "0/5 * * * * ?")
           );
        }
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
           ......
        }
    }
}

此代碼將四個內容作為單例添加到DI容器:

  • SingletonJobFactory 是前面介紹的,用于創建作業實體,
  • 一個ISchedulerFactory的實作,使用內置的StdSchedulerFactory,它可以處理調度和管理作業
  • HelloWorldJob作業本身
  • 一個型別為HelloWorldJob,并包含一個五秒鐘運行一次的Cron運算式的JobSchedule的實體化物件,

現在我們已經完成了大部分基礎作業,只缺少一個將他們組合在一起的、QuartzHostedService了,

創建QuartzHostedService

QuartzHostedServiceIHostedService的一個實作,設定了Quartz調度程式,并且啟用它并在后臺運行,由于Quartz的設計,我們可以在IHostedService中直接實作它,而不是從基BackgroundService類派生更常見的方法,該服務的完整代碼在下面列出,稍后我將對其進行詳細描述,

using Microsoft.Extensions.Hosting;
using Quartz;
using Quartz.Spi;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace QuartzHostedService
{
    public class QuartzHostedService : IHostedService
    {
        private readonly ISchedulerFactory _schedulerFactory;
        private readonly IJobFactory _jobFactory;
        private readonly IEnumerable<JobSchedule> _jobSchedules;

        public QuartzHostedService(ISchedulerFactory schedulerFactory, IJobFactory jobFactory, IEnumerable<JobSchedule> jobSchedules)
        {
            _schedulerFactory = schedulerFactory ?? throw new ArgumentNullException(nameof(schedulerFactory));
            _jobFactory = jobFactory ?? throw new ArgumentNullException(nameof(jobFactory));
            _jobSchedules = jobSchedules ?? throw new ArgumentNullException(nameof(jobSchedules));
        }
        public IScheduler Scheduler { get; set; }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            Scheduler.JobFactory = _jobFactory;
            foreach (var jobSchedule in _jobSchedules)
            {
                var job = CreateJob(jobSchedule);
                var trigger = CreateTrigger(jobSchedule);
                await Scheduler.ScheduleJob(job, trigger, cancellationToken);
                jobSchedule.JobStatu = JobStatus.Scheduling;
            }
            await Scheduler.Start(cancellationToken);
            foreach (var jobSchedule in _jobSchedules)
            {
                jobSchedule.JobStatu = JobStatus.Running;
            }
        }

        public async Task StopAsync(CancellationToken cancellationToken)
        {
            await Scheduler?.Shutdown(cancellationToken);
            foreach (var jobSchedule in _jobSchedules)
            {
             
                jobSchedule.JobStatu = JobStatus.Stopped;
            }
        }

        private static IJobDetail CreateJob(JobSchedule schedule)
        {
            var jobType = schedule.JobType;
            return JobBuilder
                .Create(jobType)
                .WithIdentity(jobType.FullName)
                .WithDescription(jobType.Name)
                .Build();
        }

        private static ITrigger CreateTrigger(JobSchedule schedule)
        {
            return TriggerBuilder
                .Create()
                .WithIdentity($"{schedule.JobType.FullName}.trigger")
                .WithCronSchedule(schedule.CronExpression)
                .WithDescription(schedule.CronExpression)
                .Build();
        }
    }
}

QuartzHostedService有三個依存依賴項:我們在Startup中配置的ISchedulerFactoryIJobFactory,還有一個就是IEnumerable<JobSchedule>,我們僅向DI容器中添加了一個JobSchedule物件(即HelloWorldJob),但是如果您在DI容器中注冊更多的作業計劃,它們將全部注入此處(當然,你也可以通過資料庫來進行獲取,再加以UI控制,是不是就實作了一個可視化的后臺調度了呢?自己想象吧~),

StartAsync方法將在應用程式啟動時被呼叫,因此這里就是我們配置Quartz的地方,我們首先一個IScheduler的實體,將其分配給屬性以供后面使用,然后將注入的JobFactory實體設定給調度程式:

 public async Task StartAsync(CancellationToken cancellationToken)
        {
            Scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
            Scheduler.JobFactory = _jobFactory;
            ...
        }

接下來,我們回圈注入作業計劃,并為每一個作業使用在類的結尾處定義的CreateJobCreateTrigger輔助方法在創建一個Quartz的IJobDetailITrigger,如果您不喜歡這部分的作業方式,或者需要對配置進行更多控制,則可以通過按需擴展JobScheduleDTO 來輕松自定義它,

public async Task StartAsync(CancellationToken cancellationToken)
{
    // ...
   foreach (var jobSchedule in _jobSchedules)
            {
                var job = CreateJob(jobSchedule);
                var trigger = CreateTrigger(jobSchedule);
                await Scheduler.ScheduleJob(job, trigger, cancellationToken);
                jobSchedule.JobStatu = JobStatus.Scheduling;
            }
    // ...
}

private static IJobDetail CreateJob(JobSchedule schedule)
{
    var jobType = schedule.JobType;
    return JobBuilder
        .Create(jobType)
        .WithIdentity(jobType.FullName)
        .WithDescription(jobType.Name)
        .Build();
}

private static ITrigger CreateTrigger(JobSchedule schedule)
{
    return TriggerBuilder
        .Create()
        .WithIdentity($"{schedule.JobType.FullName}.trigger")
        .WithCronSchedule(schedule.CronExpression)
        .WithDescription(schedule.CronExpression)
        .Build();
}

最后,一旦所有作業都被安排好,您就可以呼叫它的Scheduler.Start()來在后臺實際開始Quartz.NET計劃程式的處理,當應用程式關閉時,框架將呼叫StopAsync(),此時您可以呼叫Scheduler.Stop()以安全地關閉調度程式行程,

public async Task StopAsync(CancellationToken cancellationToken)
{
    await Scheduler?.Shutdown(cancellationToken);
}

您可以使用AddHostedService()擴展方法在托管服務Startup.ConfigureServices中注入我們的后臺服務:

public void ConfigureServices(IServiceCollection services)
{
    // ...
    services.AddHostedService<QuartzHostedService>();
}

如果運行該應用程式,則應該看到每隔5秒運行一次后臺任務并寫入控制臺中(或配置日志記錄的任何地方)

image-20200406151153107

在作業中使用作用域服務

這篇文章中描述的實作存在一個大問題:您只能創建Singleton或Transient作業,這意味著您不能使用注冊為作用域服務的任何依賴項,例如,您將無法將EF Core的 DatabaseContext注入您的IJob實作中,因為您會遇到Captive Dependency問題,

解決這個問題也不是很難:您可以注入IServiceProvider并創建自己的作用域,例如,如果您需要在HelloWorldJob中使用作用域服務,則可以使用以下內容:

public class HelloWorldJob : IJob
{
    // 注入DI provider
    private readonly IServiceProvider _provider;
    public HelloWorldJob( IServiceProvider provider)
    {
        _provider = provider;
    }

    public Task Execute(IJobExecutionContext context)
    {
        // 創建一個新的作用域
        using(var scope = _provider.CreateScope())
        {
            // 決議你的作用域服務
            var service = scope.ServiceProvider.GetService<IScopedService>();
            _logger.LogInformation("Hello world by yilezhu at {0}!",DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
        }

        return Task.CompletedTask;
    }
}

這樣可以確保在每次運行作業時都創建一個新的作用域,因此您可以在IJob中檢索(并處理)作用域服務,糟糕的是,這樣的寫法確實有些混亂,在下一篇文章中,我將展示另一種比較優雅的實作方式,它更簡潔,有興趣的可以關注下“DotNetCore實戰”公眾號第一時間獲取更新,

總結

在這篇文章中,我介紹了Quartz.NET,并展示了如何使用它在ASP.NET Core中的IHostedService中來調度后臺作業,這篇文章中顯示的示例最適合單例或瞬時作業,這并不理想,因為使用作用域服務顯得很笨拙,在下一篇文章中,我將展示另一種比較優雅的實作方式,它更簡潔,并使得使用作用域服務更容易,有興趣的可以關注下“DotNetCore實戰”公眾號第一時間獲取更新,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/47008.html

標籤:.NET Core

上一篇:擴展與解耦:Option模式與依賴注入結合

下一篇:IdentityServer 部署踩坑記

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more