![](/img/trans.png)
[英]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.