I have a C# application where I manage multiple IP cameras. Each camera runs a task that performs a series of operations like starting a socket, streaming data, and running an object detection model. I manage these tasks using a dictionary that holds the camera index and its corresponding CancellationTokenSource.
My issue is that when I change the settings for a camera, I want to first
cancel the existing task for that camera and then
start a new one. However, it seems like the new task starts before the old one finishes clearing, and this creates problems.
Here's the sample code:
internal static Dictionary<int, Tuple<int, CancellationTokenSource>> tokenSources = new Dictionary<int, Tuple<int, CancellationTokenSource>>();
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();
tuple.Item2.Dispose();
tokenSources.Remove(cameraIndex);
}
}
var newCts = new CancellationTokenSource();
tokenSources[cameraIndex] = Tuple.Create(selectedItemIndex, newCts);
Debug.WriteLine("CREATING NEW TASK");
await Task.Factory.StartNew(() =>
Camera.IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url),
newCts.Token,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default
);
}
public async Task Main(System.Windows.Controls.Image imageControl, int cameraIndex, CancellationToken token, string cameraUrl)
{
CameraURLs[cameraIndex] = cameraUrl;
await StartSocket(cameraIndex);
await StartStream(cameraIndex, cameraUrl);
EventHandler(cameraUrl, cameraIndex);
while (!token.IsCancellationRequested)
{
var frame = await RunYolo(cameraIndex);
if (frame != null)
await UpdateDisplay(frame, imageControl);
}
if (token.IsCancellationRequested)
{
Debug.WriteLine("Clearing Websocket!");
var ws = CameraWebsockets[cameraIndex];
if (ws != null && ws.State == System.Net.WebSockets.WebSocketState.Open)
{
await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Cancellation requested", CancellationToken.None);
await Task.Delay(500);
}
}
}
Issue: The output I see is:
CREATING NEW TASK
Clearing Websocket!
I would expect to see "Clearing Websocket!" before "CREATING NEW TASK". How can I enforce this order?
Attempts:
I tried to wait for the task to complete using await but I'm still experiencing the issue.
Any help would be greatly appreciated!
There's a couple of problems. First, your code is using Task.Factory.StartNew
for asynchronous code, which is wrong ~99.9% of the time. I am honestly surprised at how often it's used, since it's just wrong; I see it quite often. The correct replacement is Task.Run
.
Second, your code cancels the CTS but then it doesn't await
the old task before starting the next one. Your code should save the task in its data structure and then await
it before starting the next one.
Combining both of these:
static Dictionary<int, Tuple<int, CancellationTokenSource, Task>> tokenSources = new Dictionary<int, Tuple<int, CancellationTokenSource, Task>>();
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();
tuple.Item2.Dispose();
await tuple.Item3;
tokenSources.Remove(cameraIndex);
}
}
var newCts = new CancellationTokenSource();
tokenSources[cameraIndex] = Tuple.Create(selectedItemIndex, newCts, (Task)null);
Debug.WriteLine("CREATING NEW TASK");
tokenSources[cameraIndex].Item3 = Task.Run(() =>
Camera.IPCameraService.Main(SourceImageControl, selectedItemIndex, newCts.Token, url)
);
}