Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cancelling a Task started by ASP.NET

Background

The following server side code is used to spin up a long-running task that will post updates to the web front end via SignalR. I've placed a button on the front end that I then want to stop the task at the user's request.

Issue

When front end trigger's the Stop method, the tokenSource is null. I suspect, this is because it's not reaching the same instance of the ChartHub that spawned the Task.

Code

using System;
...
using System.Security.Principal;

namespace dvvWeb.Hubs
{
public class ChartHub : Hub
{
    CancellationTokenSource tokenSource;
    CancellationToken ct;
    public void Start(string serverName, string dbName, string numberOfPoints, string pollingFrequency)
    {
        ConfigModel config = new ConfigModel();

        tokenSource = new CancellationTokenSource();
        ct = tokenSource.Token;

        config.Servername = HttpUtility.UrlDecode(serverName);
        config.DbName = HttpUtility.UrlDecode(dbName);
        config.Preferences.NumberOfPoints = int.Parse(numberOfPoints);
        config.Preferences.PollingFrequency = int.Parse(pollingFrequency);

        dvvGraphingModel graphingModel = new dvvGraphingModel();

        dvvGraphingHelper graphingHelper = new dvvGraphingHelper(graphingModel, config.Servername, config.DbName);

        graphingModel = graphingHelper.Tick(config.Preferences);

        var identity = WindowsIdentity.GetCurrent();

        Task.Run(() => workItemAsync(ct, graphingModel, graphingHelper, config, identity));
    }

    public void Stop()
    {
        tokenSource.Cancel();
    }


    private async Task<CancellationToken> workItemAsync(CancellationToken ct, dvvGraphingModel graphingModel, dvvGraphingHelper graphingHelper, ConfigModel configModel, WindowsIdentity identity)
    {
        await addDataAsync(ct, graphingModel, graphingHelper, configModel, identity);
        return ct;
    }

    private async Task<CancellationToken> addDataAsync(CancellationToken ct, dvvGraphingModel graphingModel, dvvGraphingHelper graphingHelper, ConfigModel configModel, WindowsIdentity identity)
    {

        try
        {
            while(!ct.IsCancellationRequested)
            {
                identity.Impersonate();
                Clients.Caller.addPointToChart(JsonConvert.SerializeObject(graphingModel));
                System.Threading.Thread.Sleep(configModel.Preferences.PollingFrequency * 1000);
                graphingModel = graphingHelper.Tick(configModel.Preferences);

            }
        }
        catch (TaskCanceledException tce)
        {
            Trace.TraceError("Caught TaskCanceledException - signaled cancellation " + tce.Message);
        }
        return ct;
    }
}
}
like image 527
Nick Heidke Avatar asked Oct 30 '22 01:10

Nick Heidke


1 Answers

I would create a ConcurrentDictionary<string, CancellationTokenSource> where 'string' would be the user name/id or maybe

ConcurrentDictionary<IUserIdentity, CancellationTokenSource>.

Well, that´s in the case a user can only start one process at a time.

That dictionary would live in a Singleton class outside of the Hub. Your hub would be just a proxy to call methods in your singleton.

YourSingleton.Instance.Start(userId, serverName, dbName, numberOfPoints, pollingFrequency);

and

YourSingleton.Instance.Stop(userId);

Then, you could do something like:

public void Stop(string userId)
{
    CancellationTokenSource tokenSource;
    if(dictionary.TryGetValue(userId, out tokenSource))
    {
        tokenSource.Cancel();
        dictionary.TryRemove(userId out tokenSource);
    }
}
like image 116
xleon Avatar answered Nov 08 '22 03:11

xleon