我需要跟踪一个任务,并在一段时间后可能排队另一个任务,所以我想这样做的方式看起来像这样:
private Task lastTask;
public void DoSomeTask()
{
if (lastTask == null)
{
lastTask = Task.FromResult(false);
}
lastTask = lastTask.ContinueWith(t =>
{
// do some task
}).ContinueWith(t => Task.Delay(250).Wait());
}
我的问题是,如果我做这样的事情,创建可能很长的任务链就会处理旧任务,或者它们最终会永远存在,因为ContinueWith
将最后一个任务作为参数(所以这是一个关闭)。如果是这样,我如何在避免此问题的同时将任务链接起来?
有更好的方法吗?
答案 0 :(得分:2)
如果我做这样的事情,创建潜在的长任务链将是旧的任务被处理
任务不需要明确处理,因为它们不包含非托管资源。
他们最终会永远坚持下去,因为ContinueWith将最后一个任务作为一个参数(所以它是一个闭包)
它不是一个闭包。闭包是一种匿名方法,在其正文中使用来自匿名方法范围之外的变量。你没有这样做,所以你不会关闭它。但是,每个Task
都有一个跟踪其父级的字段,因此如果您使用此模式,托管的Task
对象仍然可以访问。
答案 1 :(得分:2)
看看source code of the ContinuationTaskFromTask class。它具有以下代码:
internal override void InnerInvoke()
{
// Get and null out the antecedent. This is crucial to avoid a memory
// leak with long chains of continuations.
var antecedent = m_antecedent;
Contract.Assert(antecedent != null,
"No antecedent was set for the ContinuationTaskFromTask.");
m_antecedent = null;
m_antecedent
是包含对先行提问的引用的字段。这里的开发人员明确地将它设置为null
(在不再需要它之后)以确保没有长连续链的内存泄漏,我想这是你的关注。
答案 2 :(得分:1)
Task.Delay(250).Wait()
当你在尝试异步的代码中使用Wait
时,知道你做错了什么。这是一个浪费的线程什么都不做。
以下情况会好得多:
lastTask = lastTask.ContinueWith(t =>
{
// do some task
}).ContinueWith(t => Task.Delay(250)).Unwrap();
ContinueWith
返回Task<Task>
,Unwrap
调用会将其转换为Task
,当内部任务执行时,该public void DoSomeTask()
{
if (this.lastTask == null)
this.lastTask = (Task) Task.FromResult<bool>(false);
// ISSUE: method pointer
// ISSUE: method pointer
this.lastTask = this.lastTask
.ContinueWith(
Program.<>c.<>9__2_0
?? (Program.<>c.<>9__2_0 = new Action<Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_0))))
.ContinueWith<Task>(
Program.<>c.<>9__2_1
?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1))))
.Unwrap();
}
[CompilerGenerated]
[Serializable]
private sealed class <>c
{
public static readonly Program.<>c <>9;
public static Action<Task> <>9__2_0;
public static Func<Task, Task> <>9__2_1;
static <>c()
{
Program.<>c.<>9 = new Program.<>c();
}
public <>c()
{
base.\u002Ector();
}
internal void <DoSomeTask>b__2_0(Task t)
{
}
internal Task <DoSomeTask>b__2_1(Task t)
{
return Task.Delay(250);
}
}
将完成。< / p>
现在,为了回答你的问题,我们来看看编译器生成的内容:
.ContinueWith<Task>(
Program.<>c.<>9__2_1
?? (Program.<>c.<>9__2_1 = new Func<Task, Task>((object) Program.<>c.<>9, __methodptr(<DoSomeTask>b__2_1))))
这是在“告诉我所有胆量”模式中使用dotPeek反编译的。
看看这部分:
ContinueWith
internal Task <DoSomeTask>b__2_1(Task t)
{
return Task.Delay(250);
}
函数被赋予一个单独的委托。所以,那里的任何变量都没有关闭。
现在,有这个功能:
t
这里的this
是对上一个任务的引用。注意什么?它从未使用过。 JIT会将此本地标记为无法访问,GC将能够清除它。在启用优化的情况下,JIT将积极地标记符合收集条件的本地人,即使实时方法可以执行而正在由GC收集实例,如果所述实例方法没有' t在要执行的代码中引用Task
。
现在,最后一件事,TaskCreationOptions.AttachedToParent
类中有m_parent
字段,这对您的方案不利。但只要你没有使用DenyChildAttach
,你应该没问题。您可以随时添加internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions)
{
return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
}
标志以获得额外的安全性和自我记录。
这是function which deals with that:
curl xxx://udacity.github.io/ud595-shell/stuff.zip -o things.zip
所以,你应该在这里安全。如果您想确定,请在长链上运行内存分析器,并亲眼看看。