如何在C#Task API上清除挂起的任务?(How to cleanup hanging tasks on C# Task API?)

编程入门 行业动态 更新时间:2024-10-25 12:27:07
如何在C#Task API上清除挂起的任务?(How to cleanup hanging tasks on C# Task API?)

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

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.Task或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 }

这里的问题是,一旦确定了WhenAny的结果,必须清理两个任务aa.Task和tt ,否则恐怕会挂起任务。 我不知道该怎么做,任何人都能提出什么建议吗? 或者这实际上不是问题,C#会为我做这件事吗?

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

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

I have a simple function as the following:

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

The idea is simple: for any implementation of a, it must return a Task<A> to me. For this purpose, it may or may not use the parameter (of type Func<A, Task<B>). If it do, our callback will be called and it sets the result of aa, and then aa.Task will complete. Otherwise, the result of a will not depend on its parameter, so we simply return its value. In any of the situation, either aa.Task or the result of a will complete, so it should never block unless a do not uses its parameter and blocks, or the task returned by a blocks.

The above code works, for example

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 }

The problem here is, the two tasks aa.Task and tt must be cleaned up once the result of WhenAny has been determined, otherwise I am afraid there will be a leak of hanging tasks. I do not know how to do this, can any one suggest something? Or this is actually not a problem and C# will do it for me?

P.S. The name Peirce came from the famous "Peirce's Law"(((A->B)->A)->A) in propositional logic.

UPDATE: the point of matter is not "dispose" the tasks but rather stop them from running. I have tested, when I put the "main" logic in a 1000 loop it runs slowly (about 1 loop/second), and creates a lot of threads so it is a problem to solve.

最满意答案

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

编辑:

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

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

这样,您的任务可以优雅地清理而不会抛出异常。 但是,如果调用cancelToken.ThrowIfCancellationRequested()则可以传播OperationCancelledException 。 因此,您的案例中的想法是,无论先完成什么,都可以将取消发布到其他任务,这样他们就不会挂断工作。

Thanks to @Bryan Crosby's answer, I can now implement the function as the following:

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

and it works pretty well:

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

Now it is fast and not consuming to much resources. It can be even faster (about 70 times in my machine) if you do not use the async/await keyword:

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

Here the matter is, even you can detected the return value of a(20), there is no way to cancel the async block rather than throwing an OperationCanceledException and it prevents WhenAny to be optimized.

UPDATE: optimised code and compared async/await and native Task API.

UPDATE: If I can write the following code it will be ideal:

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

Here, await? a : b has value a's result if a successes, has value b if a is cancelled (like a ? b : c, the value of a's result should have the same type of b). await break will cancel the current async block.

更多推荐

本文发布于:2023-07-21 09:34:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1208997.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:挂起   如何在   Task   tasks   API

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!