简体   繁体   English

如何从TaskCompletionSource取消任务?

[英]How to cancel a task from a TaskCompletionSource?

I'm trying to create an async ProducerConsumerCollection and for that, I'm using this msdn page ( http://msdn.microsoft.com/en-us/library/hh873173.aspx (bottom of the page)). 我正在尝试创建一个异步的ProducerConsumerCollection,为此,我正在使用这个msdn页面( http://msdn.microsoft.com/en-us/library/hh873173.aspx (页面底部))。

I'm now trying to add a timeout, here is what I do : 我现在正在尝试添加超时,这是我做的:

    public async Task<T> TakeWithTimeout(int timeout)
    {
            Task<T> takeTask = this.Take();

            if (timeout <= 0 || takeTask == await Task.WhenAny(this.tasks.Take(), Task.Delay(timeout)))
            {
                return await takeTask;
            }
            else
            {
                // Timeout
                return default(T);
            }
        }
    }

The problem with this code is that, in case of timeout, it does not cancel the task created by the Take() method. 此代码的问题在于,如果超时,它不会取消Take()方法创建的任务。

Since this task has been "created" by the TaskCompletionSource, I cannot give it a cancellationToken? 由于TaskCompletionSource已经“创建”了这个任务,我不能给它一个cancelToken吗?

So, how to proceed to cancel it and properly implement this Take with timeout ? 那么,如何继续取消它并正确实现此Take with timeout?

Thanks :) 谢谢 :)

Writing a cancel-safe async -friendly producer/consumer collection is non-trivial. 编写取消安全的async友好型生产者/消费者集合并非易事。 What you need to do is change Take to accept a CancellationToken as a parameter, and it should register a handler so that when it is cancelled the TaskCompletionSource is cancelled. 您需要做的是更改Take以接受CancellationToken作为参数,它应该注册一个处理程序,以便在取消时取消TaskCompletionSource

I highly recommend you use BufferBlock<T> , which has cancellation support built-in. 我强烈建议您使用BufferBlock<T> ,它内置了取消支持。

If you can't use TPL Dataflow (eg, you're working in a PCL or have target platforms unsupported by Dataflow), then you can use the producer/consumer collections in my open-source AsyncEx library (such as AsyncProducerConsumerQueue or AsyncCollection ). 如果您不能使用TPL Dataflow(例如,您在PCL中工作或Dataflow不支持目标平台),那么您可以在我的开源AsyncEx库中使用生产者/消费者集合(例如AsyncProducerConsumerQueueAsyncCollection ) 。 These are both based on AsyncLock and AsyncConditionVariable , a design I describe briefly on my blog (which does not get into the cancellation details). 这些都是基于AsyncLockAsyncConditionVariable ,我在我的博客上简要描述了一个设计(没有进入取消细节)。 The key behind supporting cancellation in a producer/consumer collection with this design is to support cancellation in AsyncConditionVariable.WaitAsync ; 使用此设计支持生产者/消费者集合中取消的关键是支持AsyncConditionVariable.WaitAsync取消; once your condition variable type supports cancellation, then your collection will easily support it, too. 一旦您的条件变量类型支持取消,那么您的集合也将轻松支持它。

I'm just going to post my solution to the question How to cancel a task from a TaskCompletionSource because that is what I needed myself. 我只是将我的解决方案发布到如何从TaskCompletionSource取消任务的问题因为这是我自己需要的。

I'm guessing this could be used for your specific need, but it's not tied to a specific timeout functionality, so this is a general solution (or so I hope). 我猜这可以用于您的特定需求,但它不依赖于特定的超时功能,所以这是一个通用的解决方案(或者我希望如此)。

This is an extension method: 这是一种扩展方法:

    public static async Task WaitAsync<T>(this TaskCompletionSource<T> tcs, CancellationToken ctok)
    {

        CancellationTokenSource cts = null;
        CancellationTokenSource linkedCts = null;

        try {

            cts = new CancellationTokenSource();
            linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ctok);

            var exitTok = linkedCts.Token;

            Func<Task> listenForCancelTaskFnc = async () => {
                await Task.Delay(-1, exitTok).ConfigureAwait(false); 
            };

            var cancelTask = listenForCancelTaskFnc();

            await Task.WhenAny(new Task[] { tcs.Task, cancelTask }).ConfigureAwait(false);

            cts.Cancel();

        } finally {

            if(linkedCts != null) linkedCts.Dispose();

        }

    }

Usage: 用法:

    async Task TestAsync(CancellationToken ctok) {

        var tcs = new TaskCompletionSource<bool>();

        if (somethingOrTheOther) {
            tcs.TrySetResult(true);
        }

        await tcs.WaitAsync(ctok);

    }

The idea is to have a supervisory async Task waiting essentially forever until it is cancelled, which we can use to 'exit early' in case the TaskCompletionSource is not yet satisfied, but we need to exit anyhow due to a cancel request. 我们的想法是让一个监督异步任务基本上永远等待,直到它被取消,我们可以用来“提前退出”以防止TaskCompletionSource尚未满足,但我们需要因取消请求而退出。

The supervisory Task is guaranteed to be cancelled at the end of WaitAsync regardless how it falls out of the WhenAny . 保证在WaitAsync结束时取消监督任务,无论它如何脱离WhenAny Either the TaskCompletionSource is satisfied with a result, and WhenAny completes, briefly leaving the supervisory sleeper task in tact until the next line where cts.Cancel() is called, or it was cancelled with the exitToken , which is a combined token of the passed in ctok or the internal one cts.Token . TaskCompletionSource对结果感到满意,而WhenAny完成后,短暂地离开监督睡眠者任务直到调用cts.Cancel()的下一行, 或者使用exitToken取消它,这是传递的组合标记在ctok或内部的一个cts.Token

Anyhow, I hope this makes sense -- please let me know if this code has any problems... 无论如何,我希望这是有道理的 - 请告诉我这个代码是否有任何问题......

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM