我有一个简单的功能如下:
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
的结果都将完成,因此它永远不会阻止,除非不使用其参数和块,或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
的结果确定后,必须清理两个任务tt
和WhenAny
,否则我担心会挂漏任务。我不知道怎么做,任何人都能提出什么建议吗?或者这实际上不是问题,C#会为我做这件事吗?
P.S。 Peirce
这个名字来源于命题逻辑中着名的“Peirce定律”(((A->B)->A)->A
)。
更新:问题的关键不在于“处置”任务,而是阻止它们运行。我已经测试过,当我把“主”逻辑放在一个1000循环中时,它运行缓慢(大约1个循环/秒),并创建了很多线程,所以这是一个需要解决的问题。
答案 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(如a
,a ? b : c
的值结果应该具有相同类型的b)。
a
将取消当前的异步阻止。
答案 2 :(得分:0)
MS并行编程团队的Stephen Toub说:"No. Don't bother disposing of your tasks."
tldr:在大多数情况下,处理任务不会做任何事情,当任务实际分配了非托管资源时,其终结器将在收集任务对象时释放它们。