Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CancellationToken with Hangfire

First of all, I decided to use Hangfire because it can run code in a different Windows service or in fact, on a different server. I could easily perform my tasks using Task class but my logic will be running 24/7 for a very long period until it's stopped by the user and I don't think tasks can handle this. That's why I'm using Hangfire. I'm open minded to different solutions. More specifically, my logic is monitoring stuff 24/7 using web sockets.

If you look at my code below, it has Run method which spawns a new bot in a BackgroundJob from Hangfire. The problem is that when I have to stop a specific bot (let's say "Bot 1"), it should somehow identify the bot which it currently doesn't.

Hangfire's documentation is incomplete or at least I don't understand how to do that from what's written. https://docs.hangfire.io/en/latest/background-methods/using-cancellation-tokens.html

private UpdateSubscription _subscription;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();

public async Task RunAsync(string botName)
{
    var jobId = BackgroundJob.Enqueue(() => StartAsync(botName, _cts.Token));
    await _cache.SetAsync($"bot_{botName.Replace(" ", "_")}", jobId);
}

public void Start(Bot bot, CancellationToken token)
{
    // heavy logic
    _subscription = _socketClient.SubscribeToKlineUpdates(bot.CryptoPair.Symbol, bot.TimeInterval.Interval /*KlineInterval.OneHour*/, async data =>
    {
        ... logic ...

        if (token.IsCancellationRequested)
        {
            await _socketClient.Unsubscribe(_subscription);
        }
    }
}

public async Task StopAsync(string botName)
{
    var jobId = await _cache.GetAsync<string>($"bot_{botName.Replace(" ", "_")}");

    if (jobId == null)
        return;

    BackgroundJob.Delete(jobId);
}

Edit: BackgroundJob is returning the jobId as string but somehow if (token.IsCancellationRequested) is never triggered even after BackgroundJob.Delete(jobId) call.

I also used Redis to store the job id. Edited the code above too.

like image 982
nop Avatar asked Nov 07 '22 11:11

nop


1 Answers

To cancel a Hangfire Job you don't have to provide your own CancellationToken. Instead the enqueued method you provide to Hangfire needs a parameter of that type. When enqueing a job through code you provide in that case the default CancellationToken.None and Hangfire takes care to provide the real token when the method is executed. Within your method you regulary ask the token for it's state and exit if desired.

To cancel a running job, Hangfire itself watches for two event to fire the provided token. Either the server gets a shutdown request (which will put the job into a new enqueued state to run again on next startup) or a job delete request.

Here are some code sketches:

// The method initiated from Hangfire Worker with special parameters
// for cancellationToken and context, filled by Hangfire.
public async Task MyJob(int someParameter, CancellationToken cancellationToken, PerformContext context)
{
    for (int i = 0; i < 100; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        Console.WriteLine($"Processing {i} in job {context.Job.Id}");
        await DoSomething(i, cancellationToken);
    }
}

private string EnqueueMyJob(int someParameter)
{
    // When enqueuing a job, just provide some default values for the *magic parameters*
    return jobClient.Enqueue<MyTaskClass>(task => task.MyJob(someParameter, CancellationToken.None, null));
}

private void CancelMyJob(string jobId)
{
    // If the job is currently running, the cancellation token will be set.
    jobClient.Delete(jobId);
}
like image 84
Oliver Avatar answered Nov 15 '22 05:11

Oliver