使用超时处理多个 CancellationToken
Dealing with multiple CancellationTokens with timeouts
我对如何为以下情况实施取消令牌感到有些困惑。
假设我有一个方法有一个取消令牌,没有像这样指定超时。
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
Task.Delay(1000, cancellationToken)
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
但是在那个方法中我想调用另一个需要 CancellationToken
的方法,除了这次我想设置一个超时,就像这样。
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource();
innerCancellationTokenSource.CancelAfter(1000);
var innerCancellationToken = innerCancellationTokenSource.Token;
await DoSomeElseAsyncThingAsync(innerCancellationToken);
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
如何让 innerCancellationToken
遵守来自 cancellationToken
参数的取消请求?
我能想到的最好的是这样的:
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
await Task.WhenAny(
DoSomeElseAsyncThingAsync(cancellationToken),
KaboomAsync(100, cancellationToken)
);
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
public static async Task KaboomAsync(int delay, CancellationToken cancellationToken = default)
{
await Task.Delay(delay, cancellationToken);
throw new OperationCanceledException();
}
但这并不完全正确; KaboomAsync()
函数总是会崩溃,这条路看起来很崎岖。有更好的模式吗?
Post 回答我创建了这个静态 Util 方法来节省我将样板文件放入一百万次的时间。
希望对某人有用。
public static async Task<T> CancellableUnitOfWorkHelper<T>(
Func<CancellationToken, Task<T>> unitOfWordFunc,
int timeOut,
CancellationToken cancellationToken = default
)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
return await unitOfWordFunc(linkedTokenSource.Token);
}
catch (OperationCanceledException canceledException)
{
Console.WriteLine(
cancellationToken.IsCancellationRequested
? "Manual or parent Timeout {0}"
: "UnitOfWork Timeout {0}"
, canceledException
);
throw;
}
catch (Exception exception)
{
Console.WriteLine("Exception {0}", exception);
throw;
}
}
public static async Task CancellableUnitOfWorkHelper(
Func<CancellationToken, Task> unitOfWordFunc,
int timeOut,
CancellationToken cancellationToken = default
)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
await unitOfWordFunc(linkedTokenSource.Token);
}
catch (OperationCanceledException canceledException)
{
Console.WriteLine(
cancellationToken.IsCancellationRequested
? "Manual or parent Timeout {0}"
: "UnitOfWork Timeout {0}"
, canceledException
);
throw;
}
catch (Exception exception)
{
Console.WriteLine("Exception {0}", exception);
throw;
}
}
它们可以这样使用。
await Util.CancellableUnitOfWorkHelper(
token => Task.Delay(1000, token),
200
);
或
await Util.CancellableUnitOfWorkHelper(
token => Task.Delay(1000, token),
200,
someExistingToken
);
在这两个示例中,它将在 200 毫秒后超时,但第二个示例也将遵守来自 "someExistingToken" 令牌的手动取消或超时。
CancellationTokenSource
有专门针对这个场景的方法:CreateLinkedTokenSource
在您的示例中,它可能看起来像这样:
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource();
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
{
innerCancellationTokenSource.CancelAfter(1000);
await DoSomeElseAsyncThingAsync(linkedTokenSource.Token);
}
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
请注意,处置链接源很重要,否则来自父令牌源的引用将阻止它被垃圾收集。
另见 and When to dispose CancellationTokenSource?
我对如何为以下情况实施取消令牌感到有些困惑。
假设我有一个方法有一个取消令牌,没有像这样指定超时。
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
Task.Delay(1000, cancellationToken)
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
但是在那个方法中我想调用另一个需要 CancellationToken
的方法,除了这次我想设置一个超时,就像这样。
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource();
innerCancellationTokenSource.CancelAfter(1000);
var innerCancellationToken = innerCancellationTokenSource.Token;
await DoSomeElseAsyncThingAsync(innerCancellationToken);
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
如何让 innerCancellationToken
遵守来自 cancellationToken
参数的取消请求?
我能想到的最好的是这样的:
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
await Task.WhenAny(
DoSomeElseAsyncThingAsync(cancellationToken),
KaboomAsync(100, cancellationToken)
);
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
public static async Task KaboomAsync(int delay, CancellationToken cancellationToken = default)
{
await Task.Delay(delay, cancellationToken);
throw new OperationCanceledException();
}
但这并不完全正确; KaboomAsync()
函数总是会崩溃,这条路看起来很崎岖。有更好的模式吗?
Post 回答我创建了这个静态 Util 方法来节省我将样板文件放入一百万次的时间。
希望对某人有用。
public static async Task<T> CancellableUnitOfWorkHelper<T>(
Func<CancellationToken, Task<T>> unitOfWordFunc,
int timeOut,
CancellationToken cancellationToken = default
)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
return await unitOfWordFunc(linkedTokenSource.Token);
}
catch (OperationCanceledException canceledException)
{
Console.WriteLine(
cancellationToken.IsCancellationRequested
? "Manual or parent Timeout {0}"
: "UnitOfWork Timeout {0}"
, canceledException
);
throw;
}
catch (Exception exception)
{
Console.WriteLine("Exception {0}", exception);
throw;
}
}
public static async Task CancellableUnitOfWorkHelper(
Func<CancellationToken, Task> unitOfWordFunc,
int timeOut,
CancellationToken cancellationToken = default
)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource(timeOut);
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
await unitOfWordFunc(linkedTokenSource.Token);
}
catch (OperationCanceledException canceledException)
{
Console.WriteLine(
cancellationToken.IsCancellationRequested
? "Manual or parent Timeout {0}"
: "UnitOfWork Timeout {0}"
, canceledException
);
throw;
}
catch (Exception exception)
{
Console.WriteLine("Exception {0}", exception);
throw;
}
}
它们可以这样使用。
await Util.CancellableUnitOfWorkHelper(
token => Task.Delay(1000, token),
200
);
或
await Util.CancellableUnitOfWorkHelper(
token => Task.Delay(1000, token),
200,
someExistingToken
);
在这两个示例中,它将在 200 毫秒后超时,但第二个示例也将遵守来自 "someExistingToken" 令牌的手动取消或超时。
CancellationTokenSource
有专门针对这个场景的方法:CreateLinkedTokenSource
在您的示例中,它可能看起来像这样:
public static async Task DoSomeAsyncThingAsync(CancellationToken cancellationToken = default)
{
try
{
var innerCancellationTokenSource = new CancellationTokenSource();
using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(innerCancellationTokenSource.Token, cancellationToken))
{
innerCancellationTokenSource.CancelAfter(1000);
await DoSomeElseAsyncThingAsync(linkedTokenSource.Token);
}
}
catch (OperationCanceledException canceledException)
{
// Do something with canceledException
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", canceledException);
throw;
}
catch (Exception exception)
{
// Do something with exception
Console.WriteLine("DoSomeElseAsyncThingAsync {0}", exception);
throw;
}
}
请注意,处置链接源很重要,否则来自父令牌源的引用将阻止它被垃圾收集。
另见