首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >CancellationToken和CancellationTokenSource如何使用它?

CancellationToken和CancellationTokenSource如何使用它?
EN

Stack Overflow用户
提问于 2013-12-17 16:04:24
回答 3查看 34.8K关注 0票数 8

我有一个叫做Load的UI按钮。它生成一个线程,而线程又生成一个任务。任务有等待,如果任务过期,任务将被取消。Load按钮未被禁用,用户可以多次单击该按钮。每次点击之前的任务都应该被取消。

我对如何在这里使用CancellationTokenSource和CancellationToken感到困惑。下面是密码。您能建议如何使用它吗?下面的用法是否有问题?请不要异步,因为我们还没有到那里。

代码语言:javascript
运行
复制
CancellationTokenSource _source = new CancellationTokenSource();
        public void OnLoad()
        {
            //Does this cancel the previously spawned task?
            _source.Cancel();
            _source.Dispose();
            _source = new CancellationTokenSource();
            var activeToken = _source.Token;
            //Do I need to do the above all the time or is there an efficient way?

            Task.Factory.StartNew(() =>
                {
                    var child = Task.Factory.StartNew(() =>
                        {
                            Thread.Sleep(TimeSpan.FromSeconds(20));
                            activeToken.ThrowIfCancellationRequested();
                        }, activeToken);

                    if (!child.Wait(TimeSpan.FromSeconds(5)))
                    {
                        _source.Cancel();
                    }
                });
        }

Note我需要取消以前生成的任务,并且每个派生的任务都应该有一个超时。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2013-12-17 16:38:56

首先,如果使用Visual 2012+,可以添加Microsoft.Bcl.Async包以向.NET 4.0项目添加对async和其他高级功能的支持。

如果使用的是Visual 2010,则可以使用WithTimeout库附带的ParallelExtensionsExtras扩展方法。该方法用一个TaskCompletionSource和一个计时器包装原来的任务,如果它过期了,这个定时器会调用SetCancelled

代码是这里,但实际方法很简单:

代码语言:javascript
运行
复制
    /// <summary>Creates a new Task that mirrors the supplied task but that 
    /// will be canceled after the specified timeout.</summary>
    /// <typeparam name="TResult">Specifies the type of data contained in the 
    /// task.</typeparam>
    /// <param name="task">The task.</param>
    /// <param name="timeout">The timeout.</param>
    /// <returns>The new Task that may time out.</returns>
    public static Task<TResult> WithTimeout<TResult>(this Task<TResult> task, 
                                                          TimeSpan timeout)
    {
        var result = new TaskCompletionSource<TResult>(task.AsyncState);
        var timer = new Timer(state => 
                        ((TaskCompletionSource<TResult>)state).TrySetCanceled(),
                        result, timeout, TimeSpan.FromMilliseconds(-1));
        task.ContinueWith(t =>
        {
            timer.Dispose();
            result.TrySetFromTask(t);
        }, TaskContinuationOptions.ExecuteSynchronously);
        return result.Task;
    }

您可以在创建任务后立即使用它:

代码语言:javascript
运行
复制
var myTask=Task.Factory.StartNew(()=>{...})
           .WithTimeout(TimeSpan.FromSeconds(20));

通常,您可以通过创建一个TaskCompletionSource,调用其SetResult,SetCancelled方法来响应您设置的事件或条件来创建您想要的行为。

票数 5
EN

Stack Overflow用户

发布于 2013-12-17 17:03:59

这样可以做到:

代码语言:javascript
运行
复制
    private CancellationTokenSource _cancelTasks;

    // this starts your process
    private void DoStuff()
    {
        _cancelTasks = new CancellationTokenSource();

        var task = new Task(() => { /* your actions here */ }, _cancelTasks.Token);
        task.Start();

        if (!task.Wait(5000)) _cancelTasks.Cancel();
    }
票数 10
EN

Stack Overflow用户

发布于 2013-12-17 16:40:44

您的代码中有几个错误使事情变得很混乱。

首先,您使用的是Thread.Sleep,而不是Task.Delay或其他基于计时器的方法(如果无法访问Task.Delay,我强烈建议编写自己的方法)。睡眠是阻塞等待,不能以取消令牌为条件。结果是宝贵的线程池线程被扣为人质数秒钟,即使操作被取消。这可能会导致较早的按钮按压延迟的影响。

其次,在等待结束时,您将取消_source,但这指的是源的当前_value,而不是按下按钮时的值。较早的按钮按下将取消稍后的按钮按下效果,而不是自己的。

第三,在一个线程上配置cancel令牌源,而在另一个线程上竞相取消它。幸运的是你没有得到处理对象的异常。

第四,在这种情况下使用异步将是理想的。不过,你说过你只上过.Net 4.0。

修复前三件事应该会使事情变得更容易推理:

代码语言:javascript
运行
复制
CancellationTokenSource _prevSource = new CancellationTokenSource();
public void OnButtonPress() {
    var curSource = new CancellationTokenSource();
    _prevSource.Cancel();
    _prevSource = curSource;

    MyCustomDelay(TimeSpan.FromSeconds(5), curSource.Token).ContinueWith(t => {
        curSource.Cancel();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);

    var r = MyCustomDelay(TimeSpan.FromSeconds(20), curSource.Token).ContinueWith(t => {
        curSource.ThrowIfCancellationRequested();
    }, TaskContinuationOptions.OnlyOnRanToCompletion);
    // after 5 seconds the token r's delay is conditions on is cancelled
    // so r is cancelled, due to the continuation specifying OnlyOnRanToCompletion
    // the ThrowIfCancellationRequested line won't be executed
    // although if we removed the cancel-after-5-seconds bit then it would be
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/20638952

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档