Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/wordpress/13.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# CancellationToken和CancellationTokenSource如何使用它?_C#_C# 4.0_Task Parallel Library - Fatal编程技术网

C# CancellationToken和CancellationTokenSource如何使用它?

C# CancellationToken和CancellationTokenSource如何使用它?,c#,c#-4.0,task-parallel-library,C#,C# 4.0,Task Parallel Library,我有一个名为加载的UI按钮。它产生一个线程,而线程又产生一个任务。任务上存在等待,如果任务过期,任务将被取消。加载按钮未被禁用,用户可以多次单击它。每次单击时,应取消上一个任务 我对如何使用CancellationTokenSource和CancellationToken感到困惑。下面是代码。您能否建议如何使用它,以及下面的用法是否有任何问题?没有异步请,因为我们还没有到那里 CancellationTokenSource _source = new CancellationTokenSourc

我有一个名为加载的UI按钮。它产生一个线程,而线程又产生一个任务。任务上存在等待,如果任务过期,任务将被取消。加载按钮未被禁用,用户可以多次单击它。每次单击时,应取消上一个任务

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

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();
                    }
                });
        }

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

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

如果您使用的是Visual Studio 2010,则可以使用ParallelExtensionsExtras库附带的
WithTimeout
扩展方法。该方法使用TaskCompletionSource和计时器包装原始任务,如果任务过期,计时器将调用
SetCancelled

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

    /// <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;
    }

通常,您可以通过创建TaskCompletionSource来创建所需的行为,该TaskCompletionSource调用其SetResult、SetCancelled方法以响应您设置的事件或条件。

代码中有一些错误会使事情变得混乱

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

第二,在等待结束时,您将取消源,但这指的是源的当前值,而不是按下按钮时的值。较早的按钮按下将取消以后的按钮按下效果,而不是它们自己的效果

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

第四,在这种情况下使用异步是理想的。不过,您提到您只使用了.NET4.0

解决前三个问题应使事情更容易推理:

CancellationTokenSource\u 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。仅限OnArntoCompletion);
var r=MyCustomDelay(TimeSpan.FromSeconds(20),curSource.Token)。ContinueWith(t=>{
curSource.ThrowIfCancellationRequested();
},TaskContinuationOptions。仅限OnArntoCompletion);
//5秒后,令牌r的延迟为on,条件为取消
//因此r被取消,因为continuation只指定了yonRantoCompletion
//不会执行ThrowIfCancellationRequested行
//虽然如果我们删除了5秒后取消位,那么它将是
}
这样就可以了:

    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();
    }

我认为有一种内置方法可以在某个超时后取消令牌。@SLaks不起作用,因为我在.NET 4.0上。我知道您说过不使用异步,仅供参考,您可以在4.0中使用Microsoft提供的异步(如果您使用的是VS 2012或更高版本)。任务做什么?你能定期检查里面的
CancellationToken
吗?.NET 4,没有任务。Delay@Panagiotis我修改了它,用定时器实现了一个。阻塞的成本使它值得实现。不要忘记处理
CancellationTokenSource
。还建议先将新的CancellationTokenSource保存到局部变量,并传递该变量,以防多个线程多次命中它。参见@Craig Gidney的答案
    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();
    }