如何从客户端取消异步任务
How to cancel async Task from the client
我在 ASP.Net C# web API 上有一个导入端点。 Javascript 客户端将项目列表发送到此 API 并且 API 在另一个线程(长任务)中处理此列表并立即 returns 进程的唯一 ID (GUID)。现在我需要从 CLIENT 取消后台任务。有可能以某种方式从客户端发送取消令牌吗?我试图将 CancellationToken 作为参数添加到我的控制器异步操作中,但我不知道如何从客户端传递它。为了简化,我们可以使用 Postman 应用程序作为客户端。
示例服务器端
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
foreach (var item in queue)
{
var result = await ProcessItemList(item, false);
resultWrapper.AddResultItem(result);
}
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
在较高级别上,您可以做的是在对工作进行排队时将进程 ID 与取消令牌源一起存储。然后你可以公开一个接受进程 ID 的新端点,从商店获取取消令牌源并取消关联的令牌:
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
var lts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var ct = lts.Token;
TokenStore.Store(resultWrapper.ProcessId, lts);
foreach (var item in queue)
{
var result = await ProcessItemList(item, ct, false);
resultWrapper.AddResultItem(result);
}
TokenStore.Remove(processId) // remove the cancellation token source from storage when doen, because there is nothing to cancel
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, CancellationToken token, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList, token);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
[Route("api/bulk/CancelImportAsync")]
public async Task<IHttpActionResult> CancelImportAsync(Guid processId)
{
var tokenSource = TokenStore.Get(processId);
tokenSource.Cancel();
TokenStore.Remove(processId) // remove the cancellation token source from storage when cancelled
}
在上面的示例中,我修改了 ProcessItemList
以接受取消令牌并将其传递给 PerformBulkOperation
,假设该方法支持取消令牌。如果没有,您可以在代码中的某些点手动调用取消令牌上的 ThrowIfCancellationRequested();
以在请求取消时停止。
我添加了一个新端点,允许您取消挂起的操作。
免责声明
您肯定需要考虑一些事情,尤其是当它是 public api 时。您可以扩展商店以接受某种安全令牌,并在请求取消时检查它是否与排队工作的安全令牌相匹配。我的回答主要集中在问题的基础上
此外,我将商店的实现留给您自己想象;-)
我在 ASP.Net C# web API 上有一个导入端点。 Javascript 客户端将项目列表发送到此 API 并且 API 在另一个线程(长任务)中处理此列表并立即 returns 进程的唯一 ID (GUID)。现在我需要从 CLIENT 取消后台任务。有可能以某种方式从客户端发送取消令牌吗?我试图将 CancellationToken 作为参数添加到我的控制器异步操作中,但我不知道如何从客户端传递它。为了简化,我们可以使用 Postman 应用程序作为客户端。
示例服务器端
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
foreach (var item in queue)
{
var result = await ProcessItemList(item, false);
resultWrapper.AddResultItem(result);
}
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
在较高级别上,您可以做的是在对工作进行排队时将进程 ID 与取消令牌源一起存储。然后你可以公开一个接受进程 ID 的新端点,从商店获取取消令牌源并取消关联的令牌:
[HttpPost]
[UserContextActionFilter]
[RequestBodyType(typeof(List<List<Item>>))]
[Route("api/bulk/ImportAsync")]
public async Task<IHttpActionResult> ImportAsync()
{
var body = await RequestHelper.GetRequestBody(this);
var queue = JsonConvert.DeserializeObject<List<List<Item>>>(body);
var resultWrapper = new AsynckResultWrapper(queue.Count);
HostingEnvironment.QueueBackgroundWorkItem(async ct =>
{
var lts = CancellationTokenSource.CreateLinkedTokenSource(ct);
var ct = lts.Token;
TokenStore.Store(resultWrapper.ProcessId, lts);
foreach (var item in queue)
{
var result = await ProcessItemList(item, ct, false);
resultWrapper.AddResultItem(result);
}
TokenStore.Remove(processId) // remove the cancellation token source from storage when doen, because there is nothing to cancel
});
return Ok(new
{
ProcessId = resultWrapper.ProcessId.ToString()
});
}
private async Task<ItemResult> ProcessItemList(<List<Item>>itemList, CancellationToken token, bool runInOneTransaction = false)
{
try
{
var result = await PerformBulkOperation(true, itemList, token);
return new ResultWrapper(result);
}
catch (Exception ex)
{
// process exception
return new ResultWrapper(ex);
}
}
[Route("api/bulk/CancelImportAsync")]
public async Task<IHttpActionResult> CancelImportAsync(Guid processId)
{
var tokenSource = TokenStore.Get(processId);
tokenSource.Cancel();
TokenStore.Remove(processId) // remove the cancellation token source from storage when cancelled
}
在上面的示例中,我修改了 ProcessItemList
以接受取消令牌并将其传递给 PerformBulkOperation
,假设该方法支持取消令牌。如果没有,您可以在代码中的某些点手动调用取消令牌上的 ThrowIfCancellationRequested();
以在请求取消时停止。
我添加了一个新端点,允许您取消挂起的操作。
免责声明
您肯定需要考虑一些事情,尤其是当它是 public api 时。您可以扩展商店以接受某种安全令牌,并在请求取消时检查它是否与排队工作的安全令牌相匹配。我的回答主要集中在问题的基础上
此外,我将商店的实现留给您自己想象;-)