如何从TaskCompletionSource取消任务?

时间:2013-07-23 19:44:45

标签: c# asynchronous

我正在尝试创建一个异步的ProducerConsumerCollection,为此,我正在使用这个msdn页面(http://msdn.microsoft.com/en-us/library/hh873173.aspx(页面底部))。

我现在正在尝试添加超时,这就是我所做的:

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

此代码的问题在于,如果超时,它不会取消Take()方法创建的任务。

由于TaskCompletionSource已经“创建”了这个任务,我不能给它一个cancelToken吗?

那么,如何取消它并正确实现此Take with timeout?

谢谢:)

2 个答案:

答案 0 :(得分:4)

编写取消安全async - 友好的生产者/消费者集合并非易事。您需要做的是更改Take以接受CancellationToken作为参数,并且应该注册处理程序,以便在取消时TaskCompletionSource被取消。

我强烈建议您使用内置取消支持的BufferBlock<T>

如果您不能使用TPL Dataflow(例如,您在PCL中工作或Dataflow不支持目标平台),那么您可以在我的开源AsyncEx library中使用生产者/消费者集合(例如AsyncProducerConsumerQueueAsyncCollection)。这些都基于AsyncLockAsyncConditionVariable,这是我简要描述的设计on my blog(没有进入取消细节)。使用此设计支持取消生产者/消费者集合的关键是支持AsyncConditionVariable.WaitAsync中的取消;一旦你的条件变量类型支持取消,那么你的收藏也很容易支持它。

答案 1 :(得分:1)

我只是将我的解决方案发布到如何从TaskCompletionSource取消任务这个问题,因为这就是我自己需要的。

我猜这可以用于您的特定需求,但它不依赖于特定的超时功能,所以这是一个通用的解决方案(或者我希望如此)。

这是一种扩展方法:

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

        }

    }

用法:

    async Task TestAsync(CancellationToken ctok) {

        var tcs = new TaskCompletionSource<bool>();

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

        await tcs.WaitAsync(ctok);

    }

我们的想法是让一个监督异步任务基本上永远等待它被取消,我们可以用{早期退出'以防TaskCompletionSource尚未满足,但我们需要退出,无论如何由于取消请求。

保证监督任务在WaitAsync结束时取消,无论其如何脱离WhenAnyTaskCompletionSource对结果感到满意,并且WhenAny完成,短暂地离开监督睡眠任务,直到调用cts.Cancel()的下一行,它已被exitToken取消,ctok是传入cts.Token或内部Thing的组合标记。

无论如何,我希望这是有道理的 - 请告诉我这段代码是否有问题......