簡體   English   中英

Web API和取消令牌

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM