异步等待Task <T>超时完成


387

我想等待Task <T>完成一些特殊规则:如果X毫秒后仍未完成,我想向用户显示一条消息。如果它在Y毫秒后仍未完成,我想自动请求cancel

我可以使用Task.ContinueWith异步地等待任务完成(即,安排在任务完成时执行的动作),但这不允许指定超时。我可以使用Task.Wait来同步等待任务完成并超时,但这会阻塞我的线程。如何异步等待任务完成并超时?


3
你是对的。我很惊讶它没有提供超时。也许是在.NET 5.0中...当然,我们可以将超时内置到任务本身中,但这不好,这样的事情必须免费。
Aliostad

4
.NET 4.5仍然需要您描述的两层超时逻辑,但确实提供了一种简单的方法来创建基于超时的方法CancellationTokenSource。构造函数有两种重载,一种重载具有整数毫秒的延迟,另一种具有TimeSpan延迟。
patridge,2012年

完整简单的lib源的位置:stackoverflow.com/questions/11831844/...

有完整源代码的最终解决方案吗?也许更复杂的示例用于通知每个线程中的错误并在WaitAll显示摘要之后?
Kiquenet

Answers:


563

这个怎么样:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

这是一篇很棒的博客文章“制作Task.TimeoutAfter方法”(来自MS Parallel Library团队),其中包含有关此类问题的更多信息

另外:根据对我的答案的评论要求,这里提供了扩展的解决方案,其中包括取消处理。请注意,将取消传递给任务和计时器意味着在代码中可以采用多种方式进行取消,因此您应确保进行测试并确信自己可以正确处理所有这些方法。不要错过各种组合,并希望您的计算机在运行时做正确的事情。

int timeout = 1000;
var task = SomeOperationAsync(cancellationToken);
if (await Task.WhenAny(task, Task.Delay(timeout, cancellationToken)) == task)
{
    // Task completed within timeout.
    // Consider that the task may have faulted or been canceled.
    // We re-await the task so that any exceptions/cancellation is rethrown.
    await task;

}
else
{
    // timeout/cancellation logic
}

86
应该提到的是,即使Task.Delay可以在长时间运行的任务之前完成,从而允许您处理超时情况,但这并不会取消长时间运行的任务本身。WhenAny只是让您知道传递给它的任务之一已经完成。您将必须实现CancellationToken并自行取消长时间运行的任务。
杰夫·舒马赫

30
还可能需要注意的是,该Task.Delay任务由系统计时器支持,无论超时如何,该计时器都会继续进行跟踪,直到超时到期为止SomeOperationAsync。因此,如果整个代码片段在一个紧密的循环中执行很多,那么您将消耗计时器的系统资源,直到它们全部超时为止。解决该问题的方法是在完成释放计时器资源后将CancellationToken您传递给的Task.Delay(timeout, cancellationToken)您取消SomeOperationAsync
Andrew Arnott 2014年

12
取消代码做得太多了。试试这个:int timeout = 1000; var cancelledTokenSource =新的CancellationTokenSource(超时); var cancelleToken = tokenSource.Token; var task = SomeOperationAsync(cancellationToken); 尝试{等待任务;//为成功完成添加代码} catch(OperationCancelledException){//为超时情况添加代码}
srm

3
通过等待@ilans,Task该任务存储的任何异常将在此时重新抛出。这使您有机会抓住OperationCanceledException(如果取消)或任何其他异常(如果出现故障)。
Andrew Arnott

3
@TomexOu:问题是如何异步等待任务完成。Task.Wait(timeout)将同步阻止而不是异步等待。
安德鲁·阿诺特

220

这是一个扩展方法版本,其中包含原始任务完成时取消超时的功能,这由Andrew Arnott在对他的答案的评论中建议。

public static async Task<TResult> TimeoutAfter<TResult>(this Task<TResult> task, TimeSpan timeout) {

    using (var timeoutCancellationTokenSource = new CancellationTokenSource()) {

        var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token));
        if (completedTask == task) {
            timeoutCancellationTokenSource.Cancel();
            return await task;  // Very important in order to propagate exceptions
        } else {
            throw new TimeoutException("The operation has timed out.");
        }
    }
}

8
给这个人几票。优雅的解决方案。如果您的电话没有返回类型,请确保只删除TResult。
卢卡斯

6
CancellationTokenSource是一次性的,应该放在一个using块中
PeterM

6
@ It'strap等待两次任务只是在第二次等待时返回结果。它不会执行两次。您可以说task.Result 两次执行相等。
M. Mimpen

7
task如果发生超时,原始任务()是否还会继续运行?
jag

6
较小的改进机会:TimeoutException有适当的默认消息。用“操作已超时”覆盖它。没有添加任何值,实际上暗示存在覆盖它的理由而引起一些混乱。
爱德华·布雷

49

您可以Task.WaitAny用来等待多个任务中的第一个。

您可以创建两个其他任务(在指定的超时后完成),然后用于WaitAny等待第一个完成的任务。如果首先完成的任务是您的“工作”任务,那么您就完成了。如果首先完成的任务是超时任务,那么您可以对超时做出反应(例如,请求取消)。


1
我已经看到我真正尊重的MVP使用的这项技术,对我来说,这似乎比公认的答案还干净。也许一个例子将有助于获得更多的选票!我会自愿去做,除非我没有足够的任务经验来确信它会有所帮助:)
GrahamMc 2013年

3
一个线程将被阻止-但是如果可以的话就没问题了。我采取的解决方案是以下解决方案,因为没有线程被阻塞。我读了一篇非常好的博客文章。
JJschk 2013年

@JJschk您提到您采用了解决方案below....那是什么?基于SO排序?
BozoJoe

如果我不希望取消较慢的任务怎么办?我想,当它完成,但与当前方法的返回..处理它
阿克Salikhov

18

那这样的东西呢?

    const int x = 3000;
    const int y = 1000;

    static void Main(string[] args)
    {
        // Your scheduler
        TaskScheduler scheduler = TaskScheduler.Default;

        Task nonblockingTask = new Task(() =>
            {
                CancellationTokenSource source = new CancellationTokenSource();

                Task t1 = new Task(() =>
                    {
                        while (true)
                        {
                            // Do something
                            if (source.IsCancellationRequested)
                                break;
                        }
                    }, source.Token);

                t1.Start(scheduler);

                // Wait for task 1
                bool firstTimeout = t1.Wait(x);

                if (!firstTimeout)
                {
                    // If it hasn't finished at first timeout display message
                    Console.WriteLine("Message to user: the operation hasn't completed yet.");

                    bool secondTimeout = t1.Wait(y);

                    if (!secondTimeout)
                    {
                        source.Cancel();
                        Console.WriteLine("Operation stopped!");
                    }
                }
            });

        nonblockingTask.Start();
        Console.WriteLine("Do whatever you want...");
        Console.ReadLine();
    }

您可以使用Task.Wait选项,而不会使用另一个Task阻塞主线程。


实际上,在此示例中,您不是在t1内部等待而是在上层任务。我将尝试做一个更详细的例子。
as-cii 2010年

14

这是一个基于最高投票答案的完整示例,它是:

int timeout = 1000;
var task = SomeOperationAsync();
if (await Task.WhenAny(task, Task.Delay(timeout)) == task) {
    // task completed within timeout
} else { 
    // timeout logic
}

此答案中实现的主要优点是已添加泛型,因此函数(或任务)可以返回值。这意味着任何现有函数都可以包装在超时函数中,例如:

之前:

int x = MyFunc();

后:

// Throws a TimeoutException if MyFunc takes more than 1 second
int x = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));

此代码需要.NET 4.5。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace TaskTimeout
{
    public static class Program
    {
        /// <summary>
        ///     Demo of how to wrap any function in a timeout.
        /// </summary>
        private static void Main(string[] args)
        {

            // Version without timeout.
            int a = MyFunc();
            Console.Write("Result: {0}\n", a);
            // Version with timeout.
            int b = TimeoutAfter(() => { return MyFunc(); },TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", b);
            // Version with timeout (short version that uses method groups). 
            int c = TimeoutAfter(MyFunc, TimeSpan.FromSeconds(1));
            Console.Write("Result: {0}\n", c);

            // Version that lets you see what happens when a timeout occurs.
            try
            {               
                int d = TimeoutAfter(
                    () =>
                    {
                        Thread.Sleep(TimeSpan.FromSeconds(123));
                        return 42;
                    },
                    TimeSpan.FromSeconds(1));
                Console.Write("Result: {0}\n", d);
            }
            catch (TimeoutException e)
            {
                Console.Write("Exception: {0}\n", e.Message);
            }

            // Version that works on tasks.
            var task = Task.Run(() =>
            {
                Thread.Sleep(TimeSpan.FromSeconds(1));
                return 42;
            });

            // To use async/await, add "await" and remove "GetAwaiter().GetResult()".
            var result = task.TimeoutAfterAsync(TimeSpan.FromSeconds(2)).
                           GetAwaiter().GetResult();

            Console.Write("Result: {0}\n", result);

            Console.Write("[any key to exit]");
            Console.ReadKey();
        }

        public static int MyFunc()
        {
            return 42;
        }

        public static TResult TimeoutAfter<TResult>(
            this Func<TResult> func, TimeSpan timeout)
        {
            var task = Task.Run(func);
            return TimeoutAfterAsync(task, timeout).GetAwaiter().GetResult();
        }

        private static async Task<TResult> TimeoutAfterAsync<TResult>(
            this Task<TResult> task, TimeSpan timeout)
        {
            var result = await Task.WhenAny(task, Task.Delay(timeout));
            if (result == task)
            {
                // Task completed within timeout.
                return task.GetAwaiter().GetResult();
            }
            else
            {
                // Task timed out.
                throw new TimeoutException();
            }
        }
    }
}

注意事项

给出此答案后,通常不要在正常操作期间在代码中引发异常,除非您绝对必须:

  • 每次抛出异常时,它都是非常繁重的操作,
  • 如果异常处于紧密循环中,则异常会使代码减慢100倍甚至更多。

仅当您绝对不能更改您要调用的功能,因此在指定的时间后超时,才使用此代码TimeSpan

该答案实际上仅适用于处理您根本无法重构以包含超时参数的第三方库。

如何编写健壮的代码

如果要编写健壮的代码,一般规则是这样的:

每个可能无限期阻塞的操作都必须有一个超时。

如果您遵守此规则,则您的代码最终将由于某种原因而失败,然后将无限期地阻塞,并且您的应用程序已永久挂起。

如果一段时间后有合理的超时时间,则您的应用将挂起一些极端的时间(例如30秒),然后显示错误并继续运行,或者重试。


11

使用Stephen Cleary出色的AsyncEx库,您可以执行以下操作:

TimeSpan timeout = TimeSpan.FromSeconds(10);

using (var cts = new CancellationTokenSource(timeout))
{
    await myTask.WaitAsync(cts.Token);
}

TaskCanceledException 如果超时将被抛出。


10

这是先前答案的稍微增强的版本。

  • 除了劳伦斯的答案外,它还会在发生超时时取消原始任务。
  • 除了sjb的答案变式2和3之外,您还可以提供CancellationToken原始任务,并且在发生超时时获得TimeoutException而不是OperationCanceledException
async Task<TResult> CancelAfterAsync<TResult>(
    Func<CancellationToken, Task<TResult>> startTask,
    TimeSpan timeout, CancellationToken cancellationToken)
{
    using (var timeoutCancellation = new CancellationTokenSource())
    using (var combinedCancellation = CancellationTokenSource
        .CreateLinkedTokenSource(cancellationToken, timeoutCancellation.Token))
    {
        var originalTask = startTask(combinedCancellation.Token);
        var delayTask = Task.Delay(timeout, timeoutCancellation.Token);
        var completedTask = await Task.WhenAny(originalTask, delayTask);
        // Cancel timeout to stop either task:
        // - Either the original task completed, so we need to cancel the delay task.
        // - Or the timeout expired, so we need to cancel the original task.
        // Canceling will not affect a task, that is already completed.
        timeoutCancellation.Cancel();
        if (completedTask == originalTask)
        {
            // original task completed
            return await originalTask;
        }
        else
        {
            // timeout
            throw new TimeoutException();
        }
    }
}

用法

InnerCallAsync可能需要很长时间才能完成。CallAsync用超时包装它。

async Task<int> CallAsync(CancellationToken cancellationToken)
{
    var timeout = TimeSpan.FromMinutes(1);
    int result = await CancelAfterAsync(ct => InnerCallAsync(ct), timeout,
        cancellationToken);
    return result;
}

async Task<int> InnerCallAsync(CancellationToken cancellationToken)
{
    return 42;
}

1
感谢您的解决方案!似乎您应该timeoutCancellation进入delayTask。目前,如果您取消取消,CancelAfterAsync可能会引发TimeoutException而不是引发TaskCanceledException,原因delayTask可能会首先完成。
AxelUser

@AxelUser,您是对的。我花了一个小时进行一堆单元测试,以了解发生了什么事情:)我假设当给定的两个任务WhenAny被同一标记取消时,WhenAny它将返回第一个任务。这个假设是错误的。我已经编辑了答案。谢谢!
约瑟夫·布拉哈

我很难弄清楚如何使用定义的Task <SomeResult>函数实际调用它;您是否有机会举一个如何称呼它的例子?
jhaagsma

1
@jhaagsma,添加了示例!
JosefBláha

@JosefBláha非常感谢!我仍在慢慢地绕过lambda样式语法,这对我而言是不会发生的-通过传递lambda函数,将令牌传递给CancelAfterAsync主体中的任务。好漂亮!
jhaagsma

8

使用计时器来处理该消息并自动取消。任务完成后,请在计时器上调用“处置”,以使它们永远不会触发。这是一个例子。将taskDelay更改为500、1500或2500以查看不同的情况:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        private static Task CreateTaskWithTimeout(
            int xDelay, int yDelay, int taskDelay)
        {
            var cts = new CancellationTokenSource();
            var token = cts.Token;
            var task = Task.Factory.StartNew(() =>
            {
                // Do some work, but fail if cancellation was requested
                token.WaitHandle.WaitOne(taskDelay);
                token.ThrowIfCancellationRequested();
                Console.WriteLine("Task complete");
            });
            var messageTimer = new Timer(state =>
            {
                // Display message at first timeout
                Console.WriteLine("X milliseconds elapsed");
            }, null, xDelay, -1);
            var cancelTimer = new Timer(state =>
            {
                // Display message and cancel task at second timeout
                Console.WriteLine("Y milliseconds elapsed");
                cts.Cancel();
            }
                , null, yDelay, -1);
            task.ContinueWith(t =>
            {
                // Dispose the timers when the task completes
                // This will prevent the message from being displayed
                // if the task completes before the timeout
                messageTimer.Dispose();
                cancelTimer.Dispose();
            });
            return task;
        }

        static void Main(string[] args)
        {
            var task = CreateTaskWithTimeout(1000, 2000, 2500);
            // The task has been started and will display a message after
            // one timeout and then cancel itself after the second
            // You can add continuations to the task
            // or wait for the result as needed
            try
            {
                task.Wait();
                Console.WriteLine("Done waiting for task");
            }
            catch (AggregateException ex)
            {
                Console.WriteLine("Error waiting for task:");
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine(e);
                }
            }
        }
    }
}

另外,异步CTP提供了TaskEx.Delay方法,该方法将为您包装计时器。这可以使您有更多的控制权来执行诸如在计时器触发时将TaskScheduler设置为继续的操作。

private static Task CreateTaskWithTimeout(
    int xDelay, int yDelay, int taskDelay)
{
    var cts = new CancellationTokenSource();
    var token = cts.Token;
    var task = Task.Factory.StartNew(() =>
    {
        // Do some work, but fail if cancellation was requested
        token.WaitHandle.WaitOne(taskDelay);
        token.ThrowIfCancellationRequested();
        Console.WriteLine("Task complete");
    });

    var timerCts = new CancellationTokenSource();

    var messageTask = TaskEx.Delay(xDelay, timerCts.Token);
    messageTask.ContinueWith(t =>
    {
        // Display message at first timeout
        Console.WriteLine("X milliseconds elapsed");
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    var cancelTask = TaskEx.Delay(yDelay, timerCts.Token);
    cancelTask.ContinueWith(t =>
    {
        // Display message and cancel task at second timeout
        Console.WriteLine("Y milliseconds elapsed");
        cts.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    task.ContinueWith(t =>
    {
        timerCts.Cancel();
    });

    return task;
}

他不希望当前线程被阻塞,即no task.Wait()
Cheng Chen 2010年

@Danny:只是为了使示例完整。在ContinueWith之后,您可以返回并让任务运行。我将更新我的答案以使其更清楚。
Quartermeister

2
@dtb:如果将t1设为Task <Task <Result >>,然后调用TaskExtensions.Unwrap,该怎么办?您可以从内部lambda返回t2,然后可以将连续性添加到展开的任务中。
Quartermeister

太棒了!完全可以解决我的问题。谢谢!我想我会使用@ AS-CII提出的解决方案,尽管我希望我也能接受您建议TaskExtensions.Unwrap的答案。
dtb

6

解决此问题的另一种方法是使用反应性扩展:

public static Task TimeoutAfter(this Task task, TimeSpan timeout, IScheduler scheduler)
{
        return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

使用下面的代码在您的单元测试中进行测试,对我有用

TestScheduler scheduler = new TestScheduler();
Task task = Task.Run(() =>
                {
                    int i = 0;
                    while (i < 5)
                    {
                        Console.WriteLine(i);
                        i++;
                        Thread.Sleep(1000);
                    }
                })
                .TimeoutAfter(TimeSpan.FromSeconds(5), scheduler)
                .ContinueWith(t => { }, TaskContinuationOptions.OnlyOnFaulted);

scheduler.AdvanceBy(TimeSpan.FromSeconds(6).Ticks);

您可能需要以下名称空间:

using System.Threading.Tasks;
using System.Reactive.Subjects;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using Microsoft.Reactive.Testing;
using System.Threading;
using System.Reactive.Concurrency;

4

上面@Kevan答案的通用版本,使用反应性扩展。

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, IScheduler scheduler)
{
    return task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

使用可选的调度程序:

public static Task<T> TimeoutAfter<T>(this Task<T> task, TimeSpan timeout, Scheduler scheduler = null)
{
    return scheduler is null 
       ? task.ToObservable().Timeout(timeout).ToTask() 
       : task.ToObservable().Timeout(timeout, scheduler).ToTask();
}

顺便说一句:发生超时时,将引发超时异常


0

如果使用BlockingCollection安排任务,生产者可以运行可能会长时间运行的任务,而使用者可以使用TryTake方法,该方法内置了超时和取消令牌。


我不得不写点东西(不想在这里放专有代码),但是这种情况是这样的。生产者将是执行可能超时的方法的代码,完成后将结果放入队列。使用者将使用超时调用trytake(),并在超时时接收令牌。生产者和使用者都是后台任务,并在需要时使用UI线程分派器向用户显示消息。
kns98

0

在紧张的网络循环中,我感觉到了这个Task.Delay()任务,CancellationTokenSource而其他答案则为我的用例提供了很多帮助。

尽管MSDN博客上Joe Hoag的“制作Task.TimeoutAfter方法”令人鼓舞,但TimeoutException由于上述原因,我还是有点厌倦使用流控制,因为人们期望超时的频率比不频繁。

因此,我接受了这一点,它还处理了博客中提到的优化:

public static async Task<bool> BeforeTimeout(this Task task, int millisecondsTimeout)
{
    if (task.IsCompleted) return true;
    if (millisecondsTimeout == 0) return false;

    if (millisecondsTimeout == Timeout.Infinite)
    {
        await Task.WhenAll(task);
        return true;
    }

    var tcs = new TaskCompletionSource<object>();

    using (var timer = new Timer(state => ((TaskCompletionSource<object>)state).TrySetCanceled(), tcs,
        millisecondsTimeout, Timeout.Infinite))
    {
        return await Task.WhenAny(task, tcs.Task) == task;
    }
}

这样是一个示例用例:

var receivingTask = conn.ReceiveAsync(ct);

while (!await receivingTask.BeforeTimeout(keepAliveMilliseconds))
{
    // Send keep-alive
}

// Read and do something with data
var data = await receivingTask;

0

安德鲁·阿诺特答案的几种变体:

  1. 如果要等待现有任务并确定它是完成还是超时,但是如果发生超时,则不想取消它:

    public static async Task<bool> TimedOutAsync(this Task task, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        if (timeoutMilliseconds == 0) {
            return !task.IsCompleted; // timed out if not completed
        }
        var cts = new CancellationTokenSource();
        if (await Task.WhenAny( task, Task.Delay(timeoutMilliseconds, cts.Token)) == task) {
            cts.Cancel(); // task completed, get rid of timer
            await task; // test for exceptions or task cancellation
            return false; // did not timeout
        } else {
            return true; // did timeout
        }
    }
  2. 如果要启动工作任务并在发生超时时取消工作:

    public static async Task<T> CancelAfterAsync<T>( this Func<CancellationToken,Task<T>> actionAsync, int timeoutMilliseconds)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var taskCts = new CancellationTokenSource();
        var timerCts = new CancellationTokenSource();
        Task<T> task = actionAsync(taskCts.Token);
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }
  3. 如果您已经创建了一个任务,如果发生超时,则要取消该任务:

    public static async Task<T> CancelAfterAsync<T>(this Task<T> task, int timeoutMilliseconds, CancellationTokenSource taskCts)
    {
        if (timeoutMilliseconds < 0 || (timeoutMilliseconds > 0 && timeoutMilliseconds < 100)) { throw new ArgumentOutOfRangeException(); }
    
        var timerCts = new CancellationTokenSource();
        if (await Task.WhenAny(task, Task.Delay(timeoutMilliseconds, timerCts.Token)) == task) {
            timerCts.Cancel(); // task completed, get rid of timer
        } else {
            taskCts.Cancel(); // timer completed, get rid of task
        }
        return await task; // test for exceptions or task cancellation
    }

另一则评论是,如果没有发生超时,这些版本将取消计时器,因此多次调用不会导致计时器堆积。

j


0

我在这里将其他一些答案的想法以及这个答案在另一个线程上重新推荐为Try样式的扩展方法。如果您想要扩展方法,但又避免了超时时的异常,则这是有好处的。

public static async Task<bool> TryWithTimeoutAfter<TResult>(this Task<TResult> task,
    TimeSpan timeout, Action<TResult> successor)
{

    using var timeoutCancellationTokenSource = new CancellationTokenSource();
    var completedTask = await Task.WhenAny(task, Task.Delay(timeout, timeoutCancellationTokenSource.Token))
                                  .ConfigureAwait(continueOnCapturedContext: false);

    if (completedTask == task)
    {
        timeoutCancellationTokenSource.Cancel();

        // propagate exception rather than AggregateException, if calling task.Result.
        var result = await task.ConfigureAwait(continueOnCapturedContext: false);
        successor(result);
        return true;
    }
    else return false;        
}     

async Task Example(Task<string> task)
{
    string result = null;
    if (await task.TryWithTimeoutAfter(TimeSpan.FromSeconds(1), r => result = r))
    {
        Console.WriteLine(result);
    }
}    

-3

绝对不要这样做,但是如果...我不能想到一个合理的理由,这是一个选择。

((CancellationTokenSource)cancellationToken.GetType().GetField("m_source",
    System.Reflection.BindingFlags.NonPublic |
    System.Reflection.BindingFlags.Instance
).GetValue(cancellationToken)).Cancel();
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.