- DotnetCore Web API
- Background Queue 背景佇列工作
- PowerShell Script
輸出結果
info: JobAPI.QueuedHostedService[0]
Queued Hosted Service is running.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: JobAPI
info: JobAPI.Controllers.WeatherForecastController[0]
加入一個 30 秒的 PowerShell 作業
info: JobAPI.Controllers.WeatherForecastController[0]
開始一個 30 秒的 PowerShell 作業 155e8c0c-13dd-43ff-b506-d09eaf0a0b2b
info: JobAPI.Controllers.WeatherForecastController[0]
加入一個 25 秒的 PowerShell 作業
info: JobAPI.Controllers.WeatherForecastController[0]
加入一個 20 秒的 PowerShell 作業
info: JobAPI.Controllers.WeatherForecastController[0]
完成一個 30 秒的 PowerShell 作業 155e8c0c-13dd-43ff-b506-d09eaf0a0b2b
info: JobAPI.Controllers.WeatherForecastController[0]
開始一個 25 秒的 PowerShell 作業 c7f00d7c-7fd8-46e4-a777-3a921bf4c25c
info: JobAPI.Controllers.WeatherForecastController[0]
完成一個 25 秒的 PowerShell 作業 c7f00d7c-7fd8-46e4-a777-3a921bf4c25c
info: JobAPI.Controllers.WeatherForecastController[0]
開始一個 20 秒的 PowerShell 作業 471b2916-cd4e-4615-a90e-fdfced026ad0
info: JobAPI.Controllers.WeatherForecastController[0]
完成一個 20 秒的 PowerShell 作業 471b2916-cd4e-4615-a90e-fdfced026ad00
首先開一個 WebAPI 專案
參照官方 MSDN 把 BackgroundTaskQueue 與 QueuedHostedService 搬進去
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
AppSetting 加入 Queue 容量的設定
AppSetting.json
{
"QueueCapacity": 10,
// ...
}
Startup 設定 DI
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
if (!int.TryParse(Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
// ...
}
Controller 注入 IBackgroundTaskQueue
private readonly IBackgroundTaskQueue _taskQueue;
public WeatherForecastController(IBackgroundTaskQueue taskQueue)
{
_taskQueue = taskQueue;
}
安裝 PowerShell 套件
Install-Package Microsoft.PowerShell.SDK
寫一下 API 來新增工作排程
[HttpGet]
public async void Get(int sec=15)
{
await _taskQueue.QueueBackgroundWorkItemAsync(async ct =>
{
(await PowerShell.Create()
.AddScript($"Start-Sleep -s {sec}")
.InvokeAsync())
.ToList()
.ForEach(Console.WriteLine);
});
}
其中 Start-Sleep -s 15
可以改成真正需要執行的 PowerShell Script
同場加映
改為呼叫外部 PowerShell Script 並傳遞參數
1.首先準備一個 PowerShell Script
c:\MyScript.ps1
param([int] $Sec)
'Waitting for {0} second...' -f $Sec
Start-Sleep -s $Sec
2.修改 API
[HttpGet]
public async void Get(int sec=15)
{
await _taskQueue.QueueBackgroundWorkItemAsync(async ct =>
{
(await PowerShell.Create()
.AddCommand(@"C:\MyScript.ps1")
.AddParameter("Sec", 10)
.InvokeAsync())
.ToList()
.ForEach(Console.WriteLine);
});
}
AddScript
吃不到AddParameter
所設定的參數AddCommand
裡面直接寫 Script 會無法執行AddParameter
可以設定多組.AddParameter("p1","1").AddParameter("p2","2")
補充:PowerShell 語法範例
- 沒有就建立該資料夾
param([string] $Path)
if(-not(Test-Path $Path)) {New-Item $Path -ItemType "directory"}
桌面建立捷徑執行程式
C:\Users\jakeu\AppData\Local\Microsoft\WindowsApps\Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe\wt.exe dotnet "D:\Users\jakeu\RiderProjects\src\PowerShellNetCoreQueue\JobAPI\bin\Release\net5.0\publish\JobAPI.dll"
參考 我的 Github Repo
https://github.com/jakeuj/PowerShellNetCoreQueue
參考 MSDN 的 Queue
https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/fundamentals/host/hosted-services/samples/3.x/BackgroundTasksSample/Program.cs
參考 MSDN 的 PowerShell
Could not load file or assembly 'Microsoft.Management.Infrastructure
http://dog0416.blogspot.com/2020/06/aspnet-core-microsoftpowershellsdk-iis.html