Web API和取消令牌

时间:2015-12-06 03:24:47

标签: c# asp.net asp.net-web-api

我正在尝试将Web API和取消令牌协同工作,但由于某些原因它似乎没有很好地发挥作用。

这是我的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方法被称为罚款。 当我点击按钮开始漫长的过程,然后点击取消时,我预计它会取消漫长的过程,并返回一条警告消息,告诉我它已被取消。

但事实并非如此。虽然有一个令牌取消请求,但它似乎没有注册,并且漫长的过程一直运行直到它完成。

任何人都可以告诉我为什么这不符合我的要求?

2 个答案:

答案 0 :(得分:3)

您的线程睡眠是确定性的,因为当您单击“取消”时,此线程不会被唤醒。此外,如果请求取消,则应在迭代期间进行检查。由于您已将令牌源设置为静态,因此您一次只能运行一个长时间运行的呼叫。这就是为什么你必须在开始长时间运行的过程之前检查是否已经启动了一个。添加了必要的锁以确保您的实例正确同步。

稍微修改了您的代码,但按预期工作。使其运行配置迭代以轻松测试。此外,睡眠时间增加到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>

答案 1 :(得分:1)

根据https://www.davepaquette.com/archive/2015/07/19/cancelling-long-running-queries-in-asp-net-mvc-and-web-api.aspx 你不需要创建一个单独的动作来取消和使用静态对象在两个动作之间共享状态(多用户应用程序的风格很糟糕)

你可以做到

xhr.abort()

在客户端上。

更新:任何现代数据访问层(例如实体框架)都应该能够在客户端的请求中止时传递取消令牌并取消长时间运行的查询。