如何实现取消并正确处理 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
方法分配了一些东西时才需要.
这是我用来 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
方法分配了一些东西时才需要.