问题描述
在 .NET Fx 4 中,我们曾经使用 this approach described here 来超时方法调用。这种方法依赖于在 .NET 5 中不起作用的 Thread.Abort()
,如here “可能令人惊讶的是,Thread.Abort 从未在 .NET Core 中实现过”.
在这种方法中非常有用的是被调用的方法(可能超时的方法)在主线程上执行,该线程用于执行发起调用的调用者方法。在此调用之前,会触发一个 异步线程 以在超时间隔期间等待,然后最终在捕获 Thread.Abort()
的主线程上调用 ThreadAbortException
并然后在 catch 处理程序中调用 Thread.ResetAbort()
。
我们如何才能对某些 .NET Core / .NET 5 代码具有相同的行为?
此 Microsoft 文档 Cancel async tasks after a period of time 显示了一些不同之处,其中可以在异步线程而不是在主线程上调用可以超时的方法。
解决方法
CancellationToken
是合作取消;如果您可以更改违规代码以定期检查令牌以查看它是否应该放弃,那么:就这样做。但是,您不能只在代码中添加 CancellationToken
并期望它开始执行(在触发时)类似于 Thread.Abort()
;这是非常不同的,因为它主动中断执行不需要被中断代码的任何合作。
有两点:
在 .NET Core/.NET5 中不再可能通过 .NET Fx 中的 Thread.Abort() / ThreadAbortException
实现的第一次抢先取消。正如@Marc Gravel 解释的那样,必须改用合作取消(其中可取消方法定期检查它是否已被取消)。因此,要取消的代码位于不接受 CancellationToken
的黑盒子中的情况无法在 .NET Core 中实现。
其次,可以在主线程上运行可取消方法,甚至不涉及池线程。以下是适用于 .NET Fx 和 .NET Core/.NET5 平台的 .NET Standard 代码示例:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace ClassLibraryNetStandard {
static class CancellationTokenTest_OpOnMainThread {
internal static void Go() {
bool b = WaitFor<int>.TryCallWithTimeout(
500.ToMilliseconds(),// timeout
OneSecondMethod,out int result);
Console.WriteLine($"OneSecondMethod() {(b ? "Executed" : "Cancelled")}");
}
static int OneSecondMethod(CancellationToken ct) {
for (var i = 0; i < 10; i++) {
Thread.Sleep(100.ToMilliseconds());
// co-operative cancellation: periodically check if CancellationRequested
if (ct.IsCancellationRequested) {
throw new TaskCanceledException();
}
}
return 123; // the result
}
static TimeSpan ToMilliseconds(this int nbMilliseconds)
=> new TimeSpan(0,nbMilliseconds);
}
static class WaitFor<TResult> {
internal static bool TryCallWithTimeout(
TimeSpan timeout,Func<CancellationToken,TResult> proc,out TResult result) {
var cts = new CancellationTokenSource(timeout);
try {
result = proc(cts.Token);
return true;
} catch (TaskCanceledException) { }
finally { cts.Dispose(); }
result = default;
return false;
}
}
}