3

I am building a C# application using Nancy API. I have an async operation that runs a very length optimization algorithm that needs to be cancelled by the user on occasion. Psuedo-code as follows:

        Get["/algorithm/run/", true] = async (parameters, ct) =>
        {
                var algy = new Algorithm();
                var result = await Task.Run(() => algy.RunAlgorithm(ct), ct);
        };

How do I go about cancelling the CancellationToken (ct), or creating a new method of cancelling the algorithm?

An alternative I have tried is something like:

var cts = new CancellationTokenSource();     
var cToken = cts.Token;

    Get["/algorithm/run/", true] = async (parameters, ct) =>
    {
      var algy = new Algorithm();
      var result = await Task.Run(() => algy.RunAlgorithm(cToken), cToken);
    };


    Get["/schedule/stop"] = _ =>
    {
        cts.Cancel();
    };

But this obviously does not work since the route is in its own async task.

I have read the article posted here: http://blog.jonathanchannon.com/2013/08/24/async-route-handling-with-nancy/ which mentions:

The CancellationToken is passed in so you can check the ct.IsCancellationRequested property to determine if you want to cooperatively cancel processing in your route handler. This property may be set for example if there is an internal error or if a piece of middleware decides to cancel the request, or the host is shutting down. If you didn't know Nancy is OWIN compliant and has been pretty much since the OWIN specification came out.

Any assistance would be much appreciated as I am new to handling threads.

Full example:

using Nancy;
using Nancy.Hosting.Self;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace NancyApp
{
class Program
{
    private static NancyHost NancyHost { get; set; }
    private static HttpClient Client { get; set; }

    static void Main(string[] args)
    {
        var configuration = new HostConfiguration()
        {
            UrlReservations = new UrlReservations() { CreateAutomatically = true }
        };

        NancyHost = new NancyHost(configuration, new Uri("http://localhost:1234"));

        NancyHost.Start();

        Client = new HttpClient();
        Client.Timeout = new TimeSpan(1, 0, 0);
        Client.BaseAddress = new Uri("http://localhost:1234");

        Console.WriteLine("Hosting...\n");

        Client.GetAsync("http://localhost:1234/run");
        System.Threading.Thread.Sleep(5000);
        Client.GetAsync("http://localhost:1234/cancel");
        Console.ReadLine();
    }
}

public class TestModule : NancyModule
{
    CancellationTokenSource cts = new CancellationTokenSource();

    public TestModule()
    {
        Get["/run", true] = async (parameters, ct) =>
        {
            Algorithm ag = new Algorithm();
            Console.Write("Running Algorithm...\n");
            var result = await Task.Run(() => ag.RunAlgorithm(cts.Token), cts.Token);

            return Response.AsText(result.ToString());
        };

        Get["/cancel"] = _ =>
        {
            Console.Write("Cancel requested recieved\n");
            cts.Cancel();
            return Response.AsText("Foo");
        };

    }
}

class Algorithm
{
    public Algorithm()
    {

    }

    public int RunAlgorithm(CancellationToken cToken)
    {
        int min = Int32.MinValue;

        while (min < Int32.MaxValue)
        {
            if (!cToken.IsCancellationRequested)
            {
                min++;
            }
            else
            {
                Console.Write("IsCancellationRequested true!\n");
                cToken.ThrowIfCancellationRequested();
            }
        }
        return min;
    }
}
}
Phrank
  • 115
  • 3
  • 12
  • I don't know Nancy, but it looks like it has its own token that it passes to you. Still, you don't have to limit yourself to checking only one token. You can use the one it passes _and_ the one you create, canceling if _either_ is set. If you need more detail, you should probably improve the question so it includes a good [mcve] so it's clear what specifically you're having trouble with. In the code example above, I don't see anything that seems particularly obvious as a hurdle. Note that you can use `CreateLinkedTokenSource()` to simplify the use of two tokens at once. – Peter Duniho Aug 15 '16 at 06:14
  • To be more clear in second example: while I did think this would work, when I call cts.Cancel(), the cToken object never returns true when I call cToken.IsCancellationRequested inside my algy.RunAlgorithm(cToken). – Phrank Aug 15 '16 at 17:25
  • _"when I call cts.Cancel(), the cToken object never returns true when I call cToken.IsCancellatio‌​nRequested inside my algy.RunAlgorithm(cT‌​oken)"_ -- that's not plausible, and certainly if you believe that's what happening is the kind of claim that really _must_ be supported by a good [mcve] that reliably reproduces that behavior. It is much more likely that you only think you cancelled the same token you passed to `RunAlgorithm()`, and instead are dealing with two different ones. But without a good MCVE, it's not possible to suggest how to fix the issue. – Peter Duniho Aug 15 '16 at 17:32
  • I have included an edit with a minimal example. Thank's for your efforts @PeterDuniho and for pointing me to that article. – Phrank Aug 15 '16 at 18:43
  • 1
    Again, I'm not familiar enough with Nancy to judge the code authoritatively. But two things stand out: first, on my PC I can iterate from `int.MinValue` to `int.MaxValue` in well under the 5 seconds you wait before calling the cancel operation. Second, are you sure that Nancy only ever creates one instance of `TestModule`? What happens if you make `cts` a `static` field? – Peter Duniho Aug 15 '16 at 18:52
  • @PeterDuniho That did the trick! I made the CancellationTokenSource static and it works! I guess I didn't realize the Nancy framework created more than one module... I'll have to re-read through some of their documentation. Thanks for your help! – Phrank Aug 15 '16 at 19:07
  • Happy to help. You should post your solution as an answer and accept it. This will help others who have a similar issue find your question, and will also show that you have in fact solved the problem, so that people looking for unanswered questions aren't distracted by this one. – Peter Duniho Aug 15 '16 at 19:32
  • With a static CancellationTokenSource you were able to run unlimited tasks until you call cancel. Once you call cancel any task will be canceled and any call to run again will be canceled as well. Is that your intention? – Sir Rufo Aug 20 '16 at 06:56
  • 1
    Btw, you may want to read http://stackoverflow.com/q/33764366/251311 as well – zerkms Aug 27 '16 at 03:21
  • @zerkms Thanks for the heads up! I didn't realize that it was bad practice and am making the appropriate adjustments! – Phrank Aug 28 '16 at 03:03

1 Answers1

1

Working corrections:

public class TestModule : NancyModule
{
    static CancellationTokenSource cts = new CancellationTokenSource();
    static CancellationToken cToken = cts.Token;

    public TestModule()
    {
        Get["/run", true] = async (parameters, ct) =>
        {
            Algorithm ag = new Algorithm();
            Console.Write("Running Algorithm...\n");
            var result = await Task.Run(() => ag.RunAlgorithm(cToken), cToken);

            return Response.AsText(result.ToString());
        };

        Get["/cancel"] = _ =>
        {
            Console.Write("Cancel requested recieved\n");
            cts.Cancel();
            cts.Dispose();
            cts = new CancellationTokenSource();
            cToken = cts.Token;
            return Response.AsText("Cancelled!");
        };
    }
}

Thanks to @Peter Duniho for the assistance.

Phrank
  • 115
  • 3
  • 12