[英]How to retrieve cancellation token in web-api action that consumes multipart/* request (.Net 5)
[英]Web API and Cancellation Token
我正在尝试使Web API和Cancellation Token一起工作,但是由于某些原因,它似乎不能很好地发挥作用。
这是我的Web API的代码。 它包含取消令牌属性和方法
public class TokenCancellationApiController : ApiController
{
private static CancellationTokenSource cTokenSource = new CancellationTokenSource();
// Create a cancellation token from CancellationTokenSource
private static CancellationToken cToken = cTokenSource.Token;
// Create a task and pass the cancellation token
[HttpGet]
public string BeginLongProcess()
{
string returnMessage = "The running process has finished!";
try
{
LongRunningFunc(cToken, 6);
}
catch (OperationCanceledException cancelEx)
{
returnMessage = "The running process has been cancelled.";
}
finally
{
cTokenSource.Dispose();
}
return returnMessage;
}
[HttpGet]
public string CancelLongProcess()
{
// cancelling task
cTokenSource.Cancel();
return "Cancellation Requested";
}
private static void LongRunningFunc(CancellationToken token, int seconds)
{
Console.WriteLine("Long running method");
for (int j = 0; j < seconds; j++)
{
Thread.Sleep(1000);
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancellation observed.");
throw new OperationCanceledException(token); // acknowledge cancellation
}
}
Console.WriteLine("Done looping");
}
}
我有以下HTML代码:
<script>
function BeginLongProcess()
{
alert("Will now send AJAX request to start long 6 second process...");
$.ajax({
url: "/api/TokenCancellationApi/BeginLongProcess",
type: "GET",
dataType: 'json',
success: function (result) {
alert(result);
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
console.error(err.Message)
}
});
}
function CancelLongProcess() {
$.ajax({
url: "/api/TokenCancellationApi/CancelLongProcess",
type: "GET",
dataType: 'json',
success: function (result) {
alert(result);
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
console.error(err.Message)
}
});
}
</script>
<form id="aForm" runat="server">
<p>
<button type="button" onclick="BeginLongProcess()">Begin Long Process</button>
</p>
<p>
<button type="button" onclick="CancelLongProcess()">Cancel Long Process</button>
</p>
</form>
Web API方法被称为罚款。 当我单击按钮开始长过程,然后单击“取消”时,我希望它取消长过程,并返回一条警告消息,告诉我它已被取消。
但是事实并非如此。 尽管有令牌取消请求,但它似乎没有注册,并且漫长的过程一直持续到完成为止。
谁能告诉我为什么这不起作用?
您的线程睡眠是确定性的,因为当您单击“取消”时,此类线程不会唤醒。 另外,如果请求取消,则应在迭代期间进行检查。 将令牌源设为静态后,一次只能运行一个长时间运行的调用。 因此,在开始长期运行的过程之前,还必须检查是否已经启动。 添加了必要的锁,以确保您的实例正确同步。
稍微修改了代码,但可以正常工作。 使它运行以进行配置的迭代,以轻松测试。 另外,将睡眠增加到5秒。 根据需要更改它们。
如果要异步运行长时间运行的方法,这也将起作用。 在begin方法中取消注释注释的代码。
public class TokenCancellationApiController : ApiController
{
private static object _lock = new object();
public static string _lastError;
// Static types will mean that you can only run
// one long running process at a time.
// If more than 1 needs to run, you will have to
// make them instance variable and manage
// threading and lifecycle
private static CancellationTokenSource cTokenSource;
private static CancellationToken cToken;
[HttpGet]
[Route("api/TokenCancellationApi/BeginLongProcess/{seconds}")]
public string BeginLongProcess(int seconds)
{
//Lock and check if process has already started or not.
lock (_lock)
{
if (null != cTokenSource)
{
return "A long running is already underway.";
}
cTokenSource = new CancellationTokenSource();
}
//if running asynchronously
//var task = Task.Factory.StartNew(() => LongRunningFunc(cTokenSource.Token, seconds));
//task.ContinueWith(Cleanup);
//return "Long running process has started!";
//if running synchronusly
try
{
LongRunningFunc(cTokenSource.Token, seconds);
}
catch(OperationCanceledException)
{
return "The running process has been cancelled";
}
catch(Exception ex)
{
_lastError = ex.Message;
return ex.Message;
}
finally
{
Cleanup(null);
}
return "Long running process has completed!";
}
[HttpGet]
public string CancelLongProcess()
{
// cancelling task
if (null != cTokenSource)
{
lock (_lock)
{
if (null != cTokenSource)
{
cTokenSource.Cancel();
}
return "Cancellation Requested";
}
}
else
{
return "Long running task already completed";
}
}
[HttpGet]
public string GetLastError()
{
return (string.IsNullOrEmpty(_lastError)) ? "No Error" : _lastError;
}
private static void Cleanup(Task task)
{
if (null != task && task.IsFaulted)
{
System.Diagnostics.Debug.WriteLine("Error encountered while running task");
_lastError = task.Exception.GetBaseException().Message;
}
lock (_lock)
{
if (null != cTokenSource)
{
cTokenSource.Dispose();
}
cTokenSource = null;
}
}
private static void LongRunningFunc(CancellationToken token, int seconds)
{
System.Diagnostics.Debug.WriteLine("Long running method");
int j = 0;
//Long running loop should always check if cancellation requested.
while(!token.IsCancellationRequested && j < seconds)
{
//Wait on token instead of deterministic sleep
//This way, thread will wakeup as soon as canellation
//is requested even if sleep time hasn't elapsed.
//Waiting 5 seconds
token.WaitHandle.WaitOne(5000);
j++;
}
if (token.IsCancellationRequested)
{
throw new OperationCanceledException();
}
System.Diagnostics.Debug.WriteLine("Done looping");
}
}
HTML部分
<script>
function BeginLongProcess()
{
alert("Will now send AJAX request to start long 6 second process...");
var seconds = $("#seconds").val();
$.ajax({
url: "/api/TokenCancellationApi/BeginLongProcess/"+seconds,
type: "GET",
dataType: 'json',
success: function (result) {
alert(result);
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
console.error(err.Message)
}
});
}
function CancelLongProcess() {
$.ajax({
url: "/api/TokenCancellationApi/CancelLongProcess",
type: "GET",
dataType: 'json',
success: function (result) {
alert(result);
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
console.error(err.Message)
}
});
}
function GetLastError() {
$.ajax({
url: "/api/TokenCancellationApi/GetLastError",
type: "GET",
dataType: 'json',
success: function (result) {
alert(result);
},
error: function (xhr, status, error) {
var err = eval("(" + xhr.responseText + ")");
console.error(err.Message)
}
});
}
</script>
<form id="form1" runat="server">
<div>
<p>
Iterations: <input id="seconds" type="text" value="10" /> <br />
<button type="button" onclick="BeginLongProcess()">Begin Long Process</button>
</p>
<p>
<button type="button" onclick="CancelLongProcess()">Cancel Long Process</button>
</p>
<p>
<button type="button" onclick="GetLastError()">Get Last Error</button>
</p>
</div>
</form>
根据https://www.davepaquette.com/archive/2015/07/19/cancelling-long-running-queries-in-asp-net-mvc-and-web-api.aspx,您无需创建一个单独的动作,用于使用静态对象取消和共享2个动作之间的状态(对于多用户应用程序,这是非常糟糕的样式)
你可以做
xhr.abort()
在客户端上。
更新:当来自客户端的请求中止时,任何现代数据访问层(例如Entity Framework)都应该能够传递取消令牌并取消长时间运行的查询。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.