How to Send a Message on a WebSocket Before Task Cancellation in C#?

67 Views Asked by At

I have an asynchronous method (IPCameraMethod) that handles IP camera streams. It starts and cancels tasks which manage these streams via WebSockets. The tasks are started in a separate class, IPCameraService, with its Main method.

When I attempt to cancel a task using a CancellationToken, the associated WebSocket is closed immediately, preventing me from sending a "Stop_Stream" command that should be executed before the WebSocket closes. This "Stop_Stream" message is crucial for gracefully shutting down the stream on the server side.

Here's a simplified version of my code:

internal static Dictionary<int, Tuple<int, CancellationTokenSource, Task, CameraType>> tokenSources = new Dictionary<int, Tuple<int, CancellationTokenSource, Task, CameraType>>();

In DeviceSettings class:

private async Task IPCameraMethod()
{
    if (string.IsNullOrWhiteSpace(textBoxUrl.Text) ||
        !int.TryParse(SourceComboBox.Name.Replace("comboBox", ""), out int cameraIndex) ||
        comboBoxSavedCameras.SelectedIndex < 0)
    {
        return;
    }

    string url = textBoxUrl.Text;
    int selectedItemIndex = comboBoxSavedCameras.SelectedIndex;

    if (tokenSources.TryGetValue(cameraIndex, out var tuple))
    {
        if (selectedItemIndex != tuple.Item1)
        {
            tuple.Item2.Cancel();

            try
            {
                await tuple.Item3; // Await the task's completion
            }

            catch (OperationCanceledException)
            {
                // Task was cancelled, expected behavior
            }
            tuple.Item2.Dispose();
            tokenSources.Remove(cameraIndex);
        }
        else
        {
            return; // If selected item is the same, we do not need to create a new task
        }
    }

    // Now that the old task has been cancelled and awaited, start the new task.
    var newCts = new CancellationTokenSource();
    Task newTask = null;
    if (Camera != null)
    {
        newTask = new TaskFactory().StartNew(() => Camera.IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url));
    }
    else
    {
        newTask = new TaskFactory().StartNew(() => (BaseViewModel as AddFaceViewModel).IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url));
    }

    Debug.WriteLine("New Task Created!");
    tokenSources[cameraIndex] = Tuple.Create(selectedItemIndex, newCts, newTask, CameraType.IP);
}

In IPCameraService class:

public async Task Main(System.Windows.Controls.Image imageControl, int cameraIndex, CancellationToken token, string cameraUrl)
{
    CameraURLs[cameraIndex] = cameraUrl;

    await StartSocket(cameraIndex, token);
    await StartStream(cameraIndex, cameraUrl, token);
    var stopStreamCompletion = new TaskCompletionSource<bool>();
    EventHandler(cameraUrl, cameraIndex, token);
    while (!token.IsCancellationRequested) // Added a token check to allow stopping the loop externally
    {
        var frame = await RunYolo(cameraIndex, token);
        if (frame != null)  
            await UpdateDisplay(frame, imageControl, token);
    }

    var Stop_Server = new
    {
        command = "Stop_Stream"
    };

    var ws = CameraWebsockets[cameraIndex];
    var Start_ServerJson = JsonConvert.SerializeObject(Stop_Server);
    var Start_ServerBytes = Encoding.UTF8.GetBytes(Start_ServerJson);
    await ws.SendAsync(new ArraySegment<byte>(Start_ServerBytes, 0, Start_ServerBytes.Length), WebSocketMessageType.Text, true, token);
    Debug.WriteLine("Websocket Cleared!");
}

The issue is with tuple.Item2.Cancel();, which is intended to cancel the ongoing task and immediately starts a clean-up sequence. I need to send the "Stop_Stream" command before this clean-up happens, but the WebSocket closes as soon as the cancellation is requested.

How can I restructure my cancellation and clean-up logic to allow sending the "Stop_Stream" message before the WebSocket is closed?

What I've tried:

  • I attempted to catch the OperationCanceledException hoping the WebSocket would still be open to send the message, but it's already closed by that point.
  • Moving the WebSocket sending logic to the catch block does not work either, as the WebSocket is not accessible after cancellation.

Question:

  • How can I ensure the "Stop_Stream" message is sent on the WebSocket before the cancellation of the task actually causes the WebSocket to close?

Additional Context:

The IPCameraMethod and Main methods are in different classes, where in which the IPCameraMethod calls the Main method. The WebSocket connection is crucial and should remain open until the "Stop_Stream" command is sent. C# .NET Framework 4.7.2 is being used.

0

There are 0 best solutions below