如何实现取消并正确处理 CancellationTokenSource

How to implement cancellation and dispose a CancellationTokenSource correctly

这是我用来 ping IP 地址列表的代码。它工作正常,除了今天我收到了一个致命的未处理异常! - System.ObjectDisposedException

private static CancellationTokenSource cts = new CancellationTokenSource();
private static CancellationToken ct;

// Source per cancellation Token
ct = cts.Token;

IsRun = true;
try
{
    LoopAndCheckPingAsync(AddressList.Select(a => a.IP).ToList()).ContinueWith((t) =>
    {
        if (t.IsFaulted)
        {
            Exception ex = t.Exception;
            while (ex is AggregateException && ex.InnerException != null)
                ex = ex.InnerException;
            Global.LOG.Log("Sonar.Start() - ContinueWith Faulted:" + ex.Message);
        }
        else
        {
            // Cancellation tokek
            if (cts != null)
            {
                cts.Dispose();          
            }
        }
    });
}
catch (Exception ex)
{
    Global.LOG.Log("Sonar.Start() - Exc:" + ex.Message);
}

由于我无法重现错误,我怀疑与 CancellationTokenSource 的 Disponse 方法有关。有任何正确处理 CancellationTokenSource 的想法吗?

我获取了事件查看器详细信息条目:

Informazioni sull'eccezione: System.ObjectDisposedException
   in System.Runtime.InteropServices.SafeHandle.DangerousAddRef(Boolean ByRef)
   in System.StubHelpers.StubHelpers.SafeHandleAddRef(System.Runtime.InteropServices.SafeHandle, Boolean ByRef)
   in Microsoft.Win32.Win32Native.SetEvent(Microsoft.Win32.SafeHandles.SafeWaitHandle)
   in System.Threading.EventWaitHandle.Set()
   in System.Net.NetworkInformation.Ping.set_InAsyncCall(Boolean)
   in System.Net.NetworkInformation.Ping.Finish(Boolean)
   in System.Net.NetworkInformation.Ping.PingCallback(System.Object, Boolean)
   in System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context(System.Object, Boolean)
   in System.Threading._ThreadPoolWaitOrTimerCallback.WaitOrTimerCallback_Context_f(System.Object)
   in System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   in System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)
   in System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback(System.Object, Boolean)

无法从您发布的代码中判断错误出处。通常,您必须检查 eception 消息 (callstack) 才能知道触发异常的确切位置。

一旦请求取消或可取消的操作已完成,请调用 Dispose。当您访问 CancellationTokenSource 或其 CancellationToken 实例的可变成员时抛出异常,当 CancellationTokenSource 被公开时。就像在已处置的实例上调用 Cancel 或在调用 Dispose 之后尝试获取对关联 CancellationToken 的引用一样。您必须确保没有代码访问已处置的实例。

您可以通过在处理时将 ​​CancellationTokenSource 属性 设置为空并在访问 CancellationTokenSource 之前添加空检查来执行此操作。您必须仔细控制 CancellationTokenSource 的生命周期。

以下示例展示了如何控制 CancellationTokenSource 的生命周期并防止对已处置实例的非法引用:

private CancellationTokenSource CancellationtokenSource { get; set; }

private void CancelCancellableOperation_OnClick(object sender, EventArgs e)
{
  // Check for null to avoid an ObjectDisposedException 
  // (or a  NullReferenceException in particular) exception.
  // The implemented pattern sets the property to null immediately after disposal (not thread-safe).
  this.CancellationTokenSource?.Cancel();
}

// Start scope of CancellationTokenSource. 
// Lifetime is managed by a try-catch-finally block and the use of 
// CancellationToken.ThrowIfCancellationRequested 
// to forcefully enter the try-catch-finally block on cancellation.
private async Task DoWorkAsync()
{
  this.CancellationTokenSource = new CancellationTokenSource();
  try
  {
    await CancellableOperationAsync(this.CancellationTokenSource.Token);
  }
  catch (OperationCanceledException)
  {
    // Do some cleanup or rollback. 
    // At this point the CancellationTokenSource is still not disposed.
  }
  finally 
  {
    // Invalidate CancellationTokenSource property to raise an NullReferenceException exception 
    // to indicate that thet access ocurred uncontrolled and requires a fix.
    // Create a local copy of the property to avoid race conditions.
    var cancellationTokenSource = this.CancellationTokenSource;
    this.CancellationTokenSource = null;

    // Dispose after cancellation 
    // or cancellable operations are completed
    cancellationTokenSource.Dispose();
  }
}

private async Task CancellableOperationAsync(CancellationToken cancellationToken)
{
  // Guarantee that CancellationTokenSource is never disposed before
  // CancellationTokenSource.Cancel was called or the cancellable operation has completed

  // Do something
  while (true)
  {
    await Task.Delay(TimeSpan.FromSeconds(10));

    // Add null check if you can't guard against premature disposal
    cancellationToken?.ThrowIfCancellationRequested();
  }
}

解决您的问题的最简单方法是 处理取消令牌源。
根据 MS 和一些 posts 取消令牌源的处理只有在它是 Linked cancellation token source 或(这里我不完全确定)如果令牌的 Register 方法分配了一些东西时才需要.