如何在C#Task API上清除挂起的任务?

时间:2012-06-07 04:06:44

标签: c# task

我有一个简单的功能如下:

    static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a)
    {
        var aa = new TaskCompletionSource<A>();
        var tt = new Task<A>(() => 
            a(b =>
            {
                aa.SetResult(b);
                return new TaskCompletionSource<B>().Task;
            }).Result
        );
        tt.Start();
        return Task.WhenAny(aa.Task, tt).Result;
    }

这个想法很简单:对于a的任何实现,它必须向我返回Task<A>。为此,它可能使用或不使用参数(类型Func<A, Task<B>)。如果是,我们将调用我们的回调并设置aa的结果,然后aa.Task将完成。否则,a的结果将不依赖于其参数,因此我们只返回其值。在任何情况下,aa.Taska的结果都将完成,因此它永远不会阻止,除非不使用其参数和块,或a块返回的任务

以上代码可以使用,例如

    static void Main(string[] args)
    {
        Func<Func<int, Task<int>>, Task<int>> t = a =>
        {
            return Task.FromResult(a(20).Result + 10);
        };
        Console.WriteLine(Peirce(t).Result); // output 20
        t = a => Task.FromResult(10);
        Console.WriteLine(Peirce(t).Result); // output 10
    }

这里的问题是,aa.Task的结果确定后,必须清理两个任务ttWhenAny,否则我担心会挂漏任务。我不知道怎么做,任何人都能提出什么建议吗?或者这实际上不是问题,C#会为我做这件事吗?

P.S。 Peirce这个名字来源于命题逻辑中着名的“Peirce定律”(((A->B)->A)->A)。

更新:问题的关键不在于“处置”任务,而是阻止它们运行。我已经测试过,当我把“主”逻辑放在一个1000循环中时,它运行缓慢(大约1个循环/秒),并创建了很多线程,所以这是一个需要解决的问题。

3 个答案:

答案 0 :(得分:3)

Task是一个托管对象。除非您介绍非托管资源,否则您不必担心Task泄漏资源。让GC清理它并让终结器处理WaitHandle

修改

如果您要取消任务,请考虑以CancellationTokenSource的形式使用合作取消。您可以通过重载将此令牌传递给任何任务,并且在每个任务内部,您可以使用以下代码:

while (someCondition)
{
    if (cancelToken.IsCancellationRequested)
        break;
}

这样,您的任务可以优雅地清理而不会抛出异常。但是,如果您致电OperationCancelledException,则可以宣传cancelToken.ThrowIfCancellationRequested()。因此,在您的情况下,无论先完成任何结束都可以将取消发送给其他任务,以便他们不会挂断工作。

答案 1 :(得分:1)

感谢@Bryan Crosby的回答,我现在可以实现以下功能:

    private class CanceledTaskCache<A>
    {
        public static Task<A> Instance;
    }

    private static Task<A> GetCanceledTask<A>()
    {
        if (CanceledTaskCache<A>.Instance == null)
        {
            var aa = new TaskCompletionSource<A>();
            aa.SetCanceled();
            CanceledTaskCache<A>.Instance = aa.Task;
        }
        return CanceledTaskCache<A>.Instance;
    }

    static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a)
    {
        var aa = new TaskCompletionSource<A>();
        Func<A, Task<B>> cb = b =>
        {
            aa.SetResult(b);
            return GetCanceledTask<B>();
        };
        return Task.WhenAny(aa.Task, a(cb)).Unwrap();
    }

并且效果很好:

    static void Main(string[] args)
    {
        for (int i = 0; i < 1000; ++i)
        {
            Func<Func<int, Task<String>>, Task<int>> t = 
                async a => (await a(20)).Length + 10;
            Console.WriteLine(Peirce(t).Result); // output 20
            t = async a => 10;
            Console.WriteLine(Peirce(t).Result); // output 10
        }
    }

现在它很快并且没有消耗太多资源。如果你不使用async / await关键字,它甚至可以更快(在我的机器中大约70次):

    static void Main(string[] args)
    {
        for (int i = 0; i < 10000; ++i)
        {
            Func<Func<int, Task<String>>, Task<int>> t =
                a => a(20).ContinueWith(ta => 
                    ta.IsCanceled ? GetCanceledTask<int>() : 
                    Task.FromResult(ta.Result.Length + 10)).Unwrap();
            Console.WriteLine(Peirce(t).Result); // output 20
            t = a => Task.FromResult(10);
            Console.WriteLine(Peirce(t).Result); // output 10
        }
    }

问题在于,即使您可以检测到a(20)的返回值,也无法取消async块而不是抛出OperationCanceledException并且它会阻止{{1}要优化。

更新:优化代码并比较async / await和native Task API。

更新:如果我可以编写以下代码,那将是理想的:

WhenAny

此处static Task<A> Peirce<A, B>(Func<Func<A, Task<B>>, Task<A>> a) { var aa = new TaskCompletionSource<A>(); return await? a(async b => { aa.SetResult(b); await break; }) : await aa.Task; } 的结果是成功await? a : b的结果,如果a被取消,则值为b(如aa ? b : c的值结果应该具有相同类型的b)。 a将取消当前的异步阻止。

答案 2 :(得分:0)

MS并行编程团队的Stephen Toub说:"No. Don't bother disposing of your tasks."

tldr:在大多数情况下,处理任务不会做任何事情,当任务实际分配了非托管资源时,其终结器将在收集任务对象时释放它们。