使用.NET 5 CancellationToken 调用可以在主线程上超时的方法

问题描述

在 .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;
      }
   }
}

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...