简体   繁体   English

Web API和取消令牌

[英]Web API and Cancellation Token

I'm trying to have a Web API and Cancellation Token working together, but it doesn't seem to play nicely for some reason. 我正在尝试使Web API和Cancellation Token一起工作,但是由于某些原因,它似乎不能很好地发挥作用。

Here is my code for my Web API. 这是我的Web API的代码。 It contains cancellation token properties, and methods 它包含取消令牌属性和方法

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");
    }
}

And I have the below HTML code: 我有以下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>

The Web API methods gets called fine. Web API方法被称为罚款。 When I click the button to begin the long process, and then hit cancel, I expected it to cancel the long process, and return an alert message telling me it was cancelled. 当我单击按钮开始长过程,然后单击“取消”时,我希望它取消长过程,并返回一条警告消息,告诉我它已被取消。

But that's not the case. 但是事实并非如此。 Although there was a token cancel request, it doesn't seem to be registering, and the long process keeps on running until it is finished. 尽管有令牌取消请求,但它似乎没有注册,并且漫长的过程一直持续到完成为止。

Can anyone please tell me why is this not working as I want it to? 谁能告诉我为什么这不起作用?

Your thread sleep is deterministic, as such thread won't wake up when you click Cancel. 您的线程睡眠是确定性的,因为当您单击“取消”时,此类线程不会唤醒。 Also, check should be done during iteration if cancel was requested. 另外,如果请求取消,则应在迭代期间进行检查。 As you have made the token source static, you can only run one long running call at a time. 将令牌源设为静态后,一次只能运行一个长时间运行的调用。 That is why you have to also check if one has already been kicked off or not before starting your long running process. 因此,在开始长期运行的过程之前,还必须检查是否已经启动。 Added necessary locks to ensure your instances are synchronized properly. 添加了必要的锁,以确保您的实例正确同步。

Modified your code a bit, but works as expected. 稍微修改了代码,但可以正常工作。 Made it run for configured iterations to test easily. 使它运行以进行配置的迭代,以轻松测试。 Also, increased sleep to 5 seconds. 另外,将睡眠增加到5秒。 Change them as needed. 根据需要更改它们。

This will also work if you want to run long running method asynchronously. 如果要异步运行长时间运行的方法,这也将起作用。 Uncomment the commented code in the begin method. 在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 part 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>

According to https://www.davepaquette.com/archive/2015/07/19/cancelling-long-running-queries-in-asp-net-mvc-and-web-api.aspx you don't need to create a separate action to cancel and share state between 2 actions using static objects(very bad style for multi user application) 根据https://www.davepaquette.com/archive/2015/07/19/cancelling-long-running-queries-in-asp-net-mvc-and-web-api.aspx,您无需创建一个单独的动作,用于使用静态对象取消和共享2个动作之间的状态(对于多用户应用程序,这是非常糟糕的样式)

You can just do 你可以做

xhr.abort()

on a client. 在客户端上。

Update: Any modern data access layer,such as Entity Framework, should be able to pass your cancellation token and cancel long running queries when a request from the client is aborted. 更新:当来自客户端的请求中止时,任何现代数据访问层(例如Entity Framework)都应该能够传递取消令牌并取消长时间运行的查询。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM