【问题标题】:Use .NET 5 CancellationToken to call a method that can timeout on the main thread使用 .NET 5 CancellationToken 调用可以在主线程上超时的方法
【发布时间】:2021-05-12 13:41:50
【问题描述】:

在 .NET Fx 4 中,我们曾经使用 this approach described here 来超时方法调用。这种方法依赖于 Thread.Abort(),它在 .NET 5 中不起作用,正如 here 所解释的那样“令人惊讶的是,从未为 .NET Core 实现 Thread.Abort”。 p>

在这种方法中超级有用的是被调用的方法(可能超时的方法)在主线程上执行,该线程用于执行发起调用的调用方方法。在此调用之前,异步线程被触发以在超时间隔期间等待,然后最终在捕获ThreadAbortException主线程上调用Thread.Abort(),然后在捕获中处理程序调用Thread.ResetAbort()

我们如何才能在某些 .NET Core / .NET 5 代码中获得相同的行为?

此 Microsoft 文档 Cancel async tasks after a period of time 显示了一些不同之处,即在 异步线程而不是在主线程上调用可以超时的方法。

【问题讨论】:

    标签: .net multithreading .net-core async-await cancellation-token


    【解决方案1】:

    CancellationToken合作取消;如果您可以更改违规代码以定期检查令牌以查看它是否应该放弃,那么:就这样做。但是,您不能只在代码中添加CancellationToken 并期望它开始像Thread.Abort() 一样开始行动(触发时);这非常不同,因为它主动中断执行不需要被中断的代码的任何合作。

    【讨论】:

    • 不,遗憾的是无法更改可中止代码,因此无法定期检查 CancellationToken。不幸的是,看起来 Thread.Abort() 功能在 .NET Core/.NET5 中无法实现,是吗?
    • @PatrickfromNDependteam 你将必须更改你的代码以进行合作取消,实际上,你不应该使用Thread.Abort,它有很大的一面 -由于在调用堆栈深处引发随机异常而产生的影响。我原以为像你这样的人,已经编写了一个主要的 .NET 代码,会知道这一点。
    • @Charlieface 当然我知道所有的理论,但在实践中生产日志显示我们正在以安全的方式使用 Thread.Abort()(感谢某些事件),它不会引起任何随机崩溃或损坏状态
    • @Patrick 建议:您可以使用基于子流程的模型并终止整个流程
    • 感谢@MarcGravell,我们考虑过这个选项,但到目前为止放弃了它。我们得出的结论是,从长远来看,现在投资(重要)开发以取消合作从长远来看是有意义的,谢谢
    【解决方案2】:

    有两点:

    在.NET Fx 中通过Thread.Abort() / ThreadAbortException 实现的第一个抢先取消在.NET Core/.NET5 中不再可能。正如@Marc Gravel 解释的那样,必须使用合作取消(cancellable 方法定期检查它是否已被取消)。因此,要取消的代码位于不接受 CancellationToken 的黑匣子中的场景无法在 .NET Core 中实现。

    其次,可以在主线程上运行cancellable方法,甚至不涉及池线程。以下是适用于 .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, 0, 0, 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;
          }
       }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-08-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多