使用 Web API 專案實作取消很簡單,只要再 Action 多一個 CancellationToken 參數就可以了,試驗一下發現並沒有那麼簡單...
這次我要觀察取消的效果,所以需要用 Console App,新增 Console App 專案,然後安裝以下套件
Install-Package Microsoft.AspNet.WebApi.OwinSelfHost
Install-Package Microsoft.Owin.Diagnostics
Install-Package Microsoft.Owin.Host.SystemWeb
Install-Package NLog.Config
Install-Package Swagger-Net
Install-Package EntityFramework
設定 Route,Swagger
public class Startup { public void Configuration(IAppBuilder app) { var httpConfig = new HttpConfiguration(); Route.Configure(httpConfig); Swagger.Configure(httpConfig); // Use the extension method provided by the WebApi.Owin library: app.UseWebApi(httpConfig); } }
預設頁面導到 swagger
public class Route { public static void Configure(HttpConfiguration httpConfig) { httpConfig.Routes.MapHttpRoute( "swagger_root", "", null, null, new RedirectHandler(message => message.RequestUri.ToString(), "swagger")); httpConfig.Routes.MapHttpRoute( "DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional}); } }
安裝 swagger-net 後,就會有一個 SwaggerConfig 檔案,我修改 class name、method name,主要是套用 CancellationTokenOperationFilter,它是讓 swagger ui 不要產生 CancellationToken 的參數
public class Swagger { public static void Configure(HttpConfiguration config) { var thisAssembly = typeof(Swagger).Assembly; config.EnableSwagger(c => { c.OperationFilter<CancellationTokenOperationFilter>(); ..... } }
CancellationToken 用法很簡單,讓 Action 參數帶有 CancellationToken 即可,cancel.ThrowIfCancellationRequested() 用來檢查是否有取消有的話就會拋出例外,也可以搭配 cancel.IsCancellationRequested 判斷,經我測試 cancel.IsCancellationRequested 也是會進入到 catch 區段
public class DefaultController : ApiController { private static readonly ILogger s_logger; static DefaultController() { if (s_logger == null) { s_logger = LogManager.GetCurrentClassLogger(); } } // GET api/default public async Task<IHttpActionResult> Get(CancellationToken cancel) { var index = 0; try { for (var i = 0; i < 10; i++) { cancel.ThrowIfCancellationRequested(); await Task.Delay(1000, cancel); index = i + 1; } s_logger.Trace("Process Done"); return this.Ok($"{index}"); } catch (Exception ex) when (ex is TaskCanceledException || ex is OperationCanceledException) { s_logger.Trace("Process Cancel"); } return new ResponseMessageResult(new HttpResponseMessage(HttpStatusCode.NoContent)); } }
預設,swagger-net 為了讓 CancellationToken 變成參數,還需要一個 Swagger Filter 把 CancellationToken 參數拿掉
public class CancellationTokenOperationFilter : IOperationFilter { public void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription) { var excludedParameters = apiDescription.ParameterDescriptions .Where(p => p.ParameterDescriptor.ParameterType == typeof(CancellationToken)) .Select(p => operation.parameters.FirstOrDefault(operationParam => operationParam.name == p.Name)) .ToArray(); foreach (var parameter in excludedParameters) operation.parameters.Remove(parameter); } }
接著 Ctrl+F5,把 Console App 叫起來,開啟瀏覽器訪問 https://localhost:44378
執行 /api/default,它應該會轉圈圈等待,這時候,把分頁關掉,Console App 就會收到 Cancel 的訊號囉
HttpClient 的取消
HttpClient 提供了許多非同步的的方法,傳入 CancellationToken 。
request.ContinueWith 用來觀察 Task 取消狀態是否如我所預期,當調用 cancelToken.Cancel() 後,task.IsCanceled 為 true
[TestMethod] public void Cancel() { var cancelToken = new CancellationTokenSource(); var url = "api/Default"; var request = s_client.GetAsync(url, cancelToken.Token); //Task.Delay(2000).Wait(); Task.Delay(1000).Wait(); var response = request.ContinueWith(task => { var status = $"Status = {task.Status}\r\n" + $"IsCanceled = {task.IsCanceled}\r\n" + $"IsCompleted = {task.IsCompleted}\r\n" + $"IsFaulted = {task.IsFaulted}"; s_logger.Trace(status); return task; }, TaskContinuationOptions.None ); cancelToken.Cancel(); //s_client.CancelPendingRequests(); Task.Delay(1000).Wait(); Task.WaitAll(response); }
HttpClient 的 CancelPendingRequests,也可以取消請求
不過,它們都無法取消 Server 的命令,看來 HttpClient 要實作取消得靠其他方式
若有謬誤,煩請告知,新手發帖請多包涵
Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET