在WPF应用程序中使用取消令牌字典时出现意外行为。

huangapple go评论76阅读模式
英文:

Unexpected behavior with Cancellation token Dictionary in a WPF Application

问题

我正在开发一个使用C#编写的WPF应用程序,该应用程序与本地和IP摄像头进行交互,利用AForge.Video.DirectShow和CancellationTokenSource来处理与视频捕获相关的任务。为了跟踪每个摄像头的任务,我使用了一个Dictionary,以摄像头索引作为键,以CancellationTokenSource作为值。

我面临的问题是,Dictionary似乎没有按预期维护状态。当我创建一个新任务时,我会存储一个新的CancellationTokenSource,但当我稍后尝试使用相同的键检索它时,它在Dictionary中找不到。

以下是我的代码的相关部分。tokenSources是一个Dictionary<int, CancellationTokenSource>,我用它来存储不同任务的取消标记。

private async void LocalCamera_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    System.Windows.Controls.ComboBox comboBoxCamera = sender as System.Windows.Controls.ComboBox;
    int cameraIndex = int.Parse(SourceComboBox.Name.Replace("comboBox", ""));
    int selectedItemIndex = comboBoxCamera.SelectedIndex;
    Image imageControl = comboBoxCamera.Tag as Image;

    Debug.WriteLine($"selectedItemIndex: {selectedItemIndex}");
    Debug.WriteLine($"cameraIndex: {cameraIndex}");
    Debug.WriteLine($"tokenSources contains cameraIndex: {tokenSources.ContainsKey(cameraIndex)}");

    // If selectedItemIndex is 0, cancel the current task if it's running
    if (selectedItemIndex == 0)
    {
        if (tokenSources.ContainsKey(cameraIndex))
        {
            var cts = tokenSources[cameraIndex];
            cts.Cancel();
            cts.Dispose();
            tokenSources.Remove(cameraIndex);
            Debug.WriteLine($"CancellationTokenSource for cameraIndex {cameraIndex} cancelled and removed.");
        }
    }
    else
    {
        // If selectedItemIndex is greater than 0, start a new task
        var newCts = new CancellationTokenSource();
        tokenSources[cameraIndex] = newCts;
        Debug.WriteLine($"New CancellationTokenSource created and stored for cameraIndex {cameraIndex}.");

        await Task.Factory.StartNew(() => LocalCameraService.Instance.CaptureCameraCallback(selectedItemIndex - 1, SourceImageControl, newCts.Token), newCts.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
    }

    Debug.WriteLine($"tokenSources count after operations: {tokenSources.Count}");
}

问题出现在我尝试与tokenSources互动时:

  1. 当我为cameraIndex存储一个新的CancellationTokenSource时,tokenSources字典的计数按预期增加到1。
  2. 但是,当我稍后检查相同的cameraIndex的tokenSources.ContainsKey(cameraIndex)时,它返回False,这不是预期的行为。

我已确认cameraIndex没有意外更改,并且在我的代码中没有看到任何地方重新初始化tokenSources。在调用LocalCamera_ComboBox_SelectionChanged方法时,程序似乎不会在不同调用之间维护tokenSources字典的状态。

我已尝试通过插入各种调试消息和使用断点来调试此问题,但我找不到任何清除或重新初始化tokenSources的地方。

我不确定是否遗漏了与WPF组件的生命周期、字典的使用或任务和取消标记的异步编程相关的内容。任何见解都将不胜感激。

英文:

I'm developing a WPF application in C# that interfaces with local and IP cameras, utilizing AForge.Video.DirectShow and CancellationTokenSource to handle tasks related to video capture. For tracking tasks for each camera, I use a Dictionary with camera index as key and CancellationTokenSource as value.

The problem I'm facing is that the Dictionary does not seem to be maintaining state as expected. I store a new CancellationTokenSource when a new task is created, but when I try to retrieve it with the same key later, it's not found in the Dictionary.

Here's the relevant section of my code. tokenSources is a Dictionary<int, CancellationTokenSource> that I use to store cancellation tokens for different tasks.

private async void LocalCamera_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    System.Windows.Controls.ComboBox comboBoxCamera = sender as System.Windows.Controls.ComboBox;
    int cameraIndex = int.Parse(SourceComboBox.Name.Replace(&quot;comboBox&quot;, &quot;&quot;));
    int selectedItemIndex = comboBoxCamera.SelectedIndex;
    Image imageControl = comboBoxCamera.Tag as Image;

    Debug.WriteLine($&quot;selectedItemIndex: {selectedItemIndex}&quot;);
    Debug.WriteLine($&quot;cameraIndex: {cameraIndex}&quot;);
    Debug.WriteLine($&quot;tokenSources contains cameraIndex: {tokenSources.ContainsKey(cameraIndex)}&quot;);

    // If selectedItemIndex is 0, cancel the current task if it&#39;s running
    if (selectedItemIndex == 0)
    {
        if (tokenSources.ContainsKey(cameraIndex))
        {
            var cts = tokenSources[cameraIndex];
            cts.Cancel();
            cts.Dispose();
            tokenSources.Remove(cameraIndex);
            Debug.WriteLine($&quot;CancellationTokenSource for cameraIndex {cameraIndex} cancelled and removed.&quot;);
        }
    }
    else
    {
        // If selectedItemIndex is greater than 0, start a new task
        var newCts = new CancellationTokenSource();
        tokenSources[cameraIndex] = newCts;
        Debug.WriteLine($&quot;New CancellationTokenSource created and stored for cameraIndex {cameraIndex}.&quot;);

        await Task.Factory.StartNew(() =&gt; LocalCameraService.Instance.CaptureCameraCallback(selectedItemIndex - 1, SourceImageControl, newCts.Token), newCts.Token, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
    }

    Debug.WriteLine($&quot;tokenSources count after operations: {tokenSources.Count}&quot;);
}

The issue arises when I'm trying to interact with tokenSources:

When I store a new CancellationTokenSource for cameraIndex, the tokenSources Dictionary count increases to 1, as expected.
However, when I check tokenSources.ContainsKey(cameraIndex) for the same cameraIndex later, it returns False, which is not expected.
I have confirmed that cameraIndex is not changing unexpectedly, and I don't see anywhere in my code where tokenSources is being re-initialized. The program does not seem to maintain the state of the tokenSources Dictionary across different calls to the LocalCamera_ComboBox_SelectionChanged method.

I've tried to debug this issue by inserting various debug messages and using breakpoints, but I can't find any point where tokenSources is getting cleared or re-initialized.

I'm not sure if I'm missing something related to the lifecycle of WPF components, the use of Dictionaries, or asynchronous programming with tasks and cancellation tokens. Any insights would be greatly appreciated.

答案1

得分: 1

看起来你需要更改这行代码:

int cameraIndex = int.Parse(SourceComboBox.Name.Replace("comboBox", ""));

为这行代码:

int cameraIndex = int.Parse(comboBoxCamera.Name.Replace("comboBox", ""));

我会在这里采用不同的方法。

private async void LocalCamera_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    var comboboxes = new ComboBox[]
    {
        comboBox1, comboBox2,
        comboBox3, comboBox4,
    };

    System.Windows.Controls.ComboBox comboBoxCamera = sender as System.Windows.Controls.ComboBox;
    int cameraIndex = Array.IndexOf(comboboxes, comboBoxCamera);
}
英文:

It appears to me that you need to change this line of code:

int cameraIndex = int.Parse(SourceComboBox.Name.Replace(&quot;comboBox&quot;, &quot;&quot;));

to this:

int cameraIndex = int.Parse(comboBoxCamera.Name.Replace(&quot;comboBox&quot;, &quot;&quot;));

I would be using a different approach here.

private async void LocalCamera_ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
	var comboboxes = new ComboBox[]
	{
		comboBox1, comboBox2,
		comboBox3, comboBox4,
	};

	System.Windows.Controls.ComboBox comboBoxCamera = sender as System.Windows.Controls.ComboBox;
	int cameraIndex = Array.IndexOf(comboboxes, comboBoxCamera);

答案2

得分: 0

经过再次审查您的代码,很明显您在存储条目时使用了错误的索引。下次您必须调试变量的值,以验证存储条目在Dictionary中的键是否与获取该条目的键相同。

您必须使用selectedItemIndex来存储条目(而不是cameraIndex)。尽管不清楚cameraIndex来自何处,但很明显它代表了旧索引,即正在运行的可取消操作的索引或ID。但您必须使用新的索引,即触发可取消操作的当前索引,来注册新的CancellationTokenSource

也许选择更好的变量名可以让您免受与此烦人且可避免的错误浪费宝贵时间的困扰。
例如,使用previousCameraIndexcameraIndexOfTaskToCancel(而不是cameraIndex),以及currentCameraIndexcameraIndexOfTaskToStart(而不是selectedCameraIndex)。现在,由于在阅读代码时的语义,您更难混淆它们。

此外,使用Task.Run(Action, CancellationToken)等效于StartNew(Action, CancellationToken, TaskCreationOptions, TaskScheduler),其中TaskCreationOptions.DenyChildAttachTaskScheduler.Default(您配置的方式)- 只是更紧凑和简单。这就是为什么引入了Task.Run

var comboBox = sender as System.Windows.Controls.ComboBox;
int cameraIndexOfTaskToCancel = int.Parse(SourceComboBox.Name.Replace("comboBox", ""));
int cameraIndexOfTaskToStart = comboBoxCamera.SelectedIndex;

if (cameraIndexOfTaskToStart == 0  
  && tokenSources.TryGetValue(cameraIndexOfTaskToCancel, out CancellationTokenSource cts))
{
  cts.Cancel();
  cts.Dispose();
  _ = tokenSources.Remove(cameraIndexOfTaskToCancel);
  Debug.WriteLine($"CancellationTokenSource for cameraIndex {cameraIndex} cancelled and removed.");
}
else if (cameraIndexOfTaskToStart > 0)  // Add explicit 'index > 0' check to avoid bugs when index is '-1'
{
  var newCts = new CancellationTokenSource();
  tokenSources[cameraIndexOfTaskToStart] = newCts;
  Debug.WriteLine($"New CancellationTokenSource created and stored for cameraIndex {selectedItemIndex}.");

  // For your scenario better use Task.Run
  await Task.Run(
    () => LocalCameraService.Instance.CaptureCameraCallback(
      cameraIndexOfTaskToStart - 1, 
      SourceImageControl, 
      newCts.Token), 
    newCts.Token);
}
英文:

After reviewing your code one more time it is obvious that you are using the wrong index to store the entry. Next time you must debug the values of the variables to verify that the key to store the entry in the Dictionary is the same as the key to obtain this entry.

You must use the selectedItemIndex to store the entry (and not the cameraIndex). Although it is not clear where cameraIndex comes from, it is clear that it represents the old index, the index or ID of the running cancellable operation. But you must register the new CancellationTokenSource with the new index i.e. the current index that is triggering the cancellable operation.

Maybe choosing better variable names would have saved you from spending precious time with this annoying and avoidable error.
For example previousCameraIndex or cameraIndexOfTaskToCancel (instead of cameraIndex) and currentCameraIndex or cameraIndexOfTaskToStart (instead of selectedCameraIndex). Now it would be more difficult for you to mix them up, because of the semantics when reading the code.

Also using Task.Run(Action, CancellationToke) is the absolute equivalent of StartNew(Action, CancellationToken, TaskCreationOptions, TaskScheduler) where TaskCreationOptions.DenyChildAttachand TaskScheduler.Default (the way you configured it) - only more compact and simpler. That's why Task.Run was introduced.

var comboBox = sender as System.Windows.Controls.ComboBox;
int cameraIndexOfTaskToCancel = int.Parse(SourceComboBox.Name.Replace(&quot;comboBox&quot;, &quot;&quot;));
int cameraIndexOfTaskToStart = comboBoxCamera.SelectedIndex;

if (cameraIndexOfTaskToStart == 0  
  &amp;&amp; tokenSources.TryGetValue(cameraIndexOfTaskToCancel, out CancellationTokenSource cts))
{
  cts.Cancel();
  cts.Dispose();
  _ = tokenSources.Remove(cameraIndexOfTaskToCancel);
  Debug.WriteLine($&quot;CancellationTokenSource for cameraIndex {cameraIndex} cancelled and removed.&quot;);
}
else if (cameraIndexOfTaskToStart &gt; 0)  // Add explicit &#39;index &gt; 0&#39; check to avoid bugs when index is &#39;-1&#39;
{
  var newCts = new CancellationTokenSource();
  tokenSources[cameraIndexOfTaskToStart] = newCts;
  Debug.WriteLine($&quot;New CancellationTokenSource created and stored for cameraIndex {selectedItemIndex}.&quot;);

  // For your scenario better use Task.Run
  await Task.Run(
    () =&gt; LocalCameraService.Instance.CaptureCameraCallback(
      cameraIndexOfTaskToStart - 1, 
      SourceImageControl, 
      newCts.Token), 
    newCts.Token);
}

huangapple
  • 本文由 发表于 2023年7月28日 01:26:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/76782158.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定