2

I have a function that starts testing. This function contains a while loop and may take up to 15 minuts, but the tests could finish sooner or the client may be disconnected. If one of these things happen i want to trigger an function that cancels that loop but not entirely sure how to do this.

This is a simplified example of what i have tried:

public class MyHub : Hub
{
    private CancellationTokenSource cancellationTokenSource;

    public async Task StartLongRunningTask()
    {
        cancellationTokenSource = new CancellationTokenSource();

        try
        {
            await LongRunningTask(cancellationTokenSource.Token);
            // Task completed successfully
        }
        catch (TaskCanceledException)
        {
            // Task was canceled by the client
            // Handle cancellation logic here
        }
        finally
        {
            cancellationTokenSource.Dispose();
        }
    }

    public void CancelLongRunningTask()
    {
        cancellationTokenSource?.Cancel();
    }

    private async Task LongRunningTask(CancellationToken ct)
    {
        // Your long-running task implementation
        // Ensure to check the cancellation token periodically and stop processing if canceled
        while (!ct.IsCancellationRequested) 
        {
            await Task.Delay(1000, cancellationToken);
            // Continue with other work...
        }
    }
}

This doesn't work and I'm not entirely sure why. Somehow if I call the CancelLongRunningTask, nothing really happends. I added some logging to make sure the function was called and it was, but while it says cancelled in the cancelLongRunningTask. It isn't cancelled in the actual LongRunningTask. Can someone help me out here?

Edited

Here is my real code for context:

public class DeviceTestHub : Hub<IDeviceTestClient> 
{
private readonly ILogger<DeviceTestHub> _logger;
private readonly IMemoryCache _cache;
private readonly IMessageProcessor _messageProcessor;
private readonly IDeviceTestHandler _deviceTestHandler;
private readonly TimeSpan _cacheExpirationTime = TimeSpan.FromMinutes(15);
private CancellationTokenSource _cts;


public DeviceTestHub(IDeviceTestHandler deviceTestHandler, IMemoryCache memoryCache,
    ILogger<DeviceTestHub> logger, IMessageProcessor messageProcessor)
{
    _cache = memoryCache;
    _logger = logger;
    _messageProcessor = messageProcessor;
    _deviceTestHandler = deviceTestHandler;
}

public async Task StartTesting(string imei, DateTime startDate)
{
    try
    {
        _cts = new CancellationTokenSource();

        await LongRunningTest(imei, startDate, _cts.Token);
    }
    catch (OperationCanceledException e)
    {
        await Clients.Caller.SendMessage($"Test took longer than 12 minutes and is cancelled for imei: {imei}");
        await _messageProcessor.Dispose(imei);
        await OnDisconnectedAsync(e);
    }
    catch (Exception ex)
    {
        _logger.LogError($"Exception occurred in websocket for imei: {{Imei}}, {nameof(Exception)}: {{Exception}}",
            imei, ex.Message);
        await _messageProcessor.Dispose(imei);
        await Clients.Group(imei).SendMessage(ex.Message);
    }
    finally
    {
        _cts.Dispose();
    }
}

public void CancelTesting()
{
    _logger.LogInformation("================================= Cancel testing =================================");
    _cts?.Cancel();
}

private async Task LongRunningTest(string imei, DateTime startDate, CancellationToken ct)
{
    //Clear session before start testing
    await _messageProcessor.Dispose(imei);

    //Do the actual tests
    //Get raw message from Message Session, send it to the frontend but also pass it to the test handler
    var socketTests =
        _cache.Get<List<SocketModel>>($"{MemoryCacheKeys.DeviceTest}-{imei}")
        ?? await _deviceTestHandler.GetTests(imei);

    var messages = new List<RawMessageDTO>();
    
    _cts.Token.ThrowIfCancellationRequested();

    while (!_cts.IsCancellationRequested)
    {
        if (socketTests.Any(x => x.State == State.NotStarted))
        {
            //Loop through tests and return the first test that is not started yet and set it to loading
            var test = _deviceTestHandler.SetTestActive(socketTests, _cts.Token);

            //Send the changed test to frontend
            if (test != null) await Clients.Group(imei).UpdateTest(test);
        }

        //Get raw message from Message Session
        var message = await _messageProcessor.ReceiveMessage(imei, _cts.Token);

        if (message.IsNullOrEmpty() && messages.IsNullOrEmpty())
        {
            await Task.Delay(3000, _cts.Token);
            continue;
        }

        if (!message.IsNullOrEmpty())
        {
            var rawMsg = await _deviceTestHandler.ConvertMessageToRawMessage(message);

            //Send raw message to frontend
            await Clients.Caller.SendRawMessage(JsonSerializer.Serialize(rawMsg));

            messages.Add(rawMsg);
        }

        var activeTest = socketTests.FirstOrDefault(x => x.State is State.Loading or State.Inprogress);

        if (activeTest is null) return;

        var executedTest = await _deviceTestHandler.ExecuteTest(imei, activeTest, messages, startDate);

        if (executedTest != null)
        {
            //Send the changed test to frontend
            await Clients.Group(imei).UpdateTest(executedTest);
        }
        
        _cts.Token.ThrowIfCancellationRequested();
    }
}
}
  • 1
    [Your simplified code working fine](https://dotnetfiddle.net/Rpa54F) so the problem is your real code ... *It isn't cancelled in the actual LongRunningTask.* yes, it is ... it just leaves `LongRunningTask` as `Task.Delay` will throw an error – Selvin Jul 27 '23 at 09:17
  • Than i shall add my real code for context! – Thimo Luijsterburg Jul 27 '23 at 09:39
  • fx if code "stucks" in `Clients.Caller.SendRawMessage` then you cannot cancell it ... if you have `await i.StartTesting(...); i.Cancel();` Cancel will never executed ... `_cts.IsCancellationRequested == true` when at given loop state you will not get an Exception but leave `LongRunningTest` gracefully ... you can call StartTesting and cts can be different .... and so on – Selvin Jul 27 '23 at 09:58
  • I tried everything but the LongRunningTest in the example above is not cancelled. The cancel method is triggered for sure. But the actual loop somehow doesn't have the updated version of the token or has it's own token and I don't know how or why? If I log the "IsCancellationRequested" prop in the CancelTesting method, it says cancelled. But when I then log it in the while loop, it's still not cancelled but how? – Thimo Luijsterburg Jul 27 '23 at 10:44

0 Answers0