链接任务有延迟

Mat*_*and 7 c# task-parallel-library

我需要跟踪一个任务,并可能在一段时间后排队另一个任务,所以我想这样做的方式看起来像这样:

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());
} 
Run Code Online (Sandbox Code Playgroud)

我的问题是,如果我做这样的事情,创建潜在的长链任务就会处理旧任务,或者它们最终会永远存在,因为ContinueWith它将最后一个任务作为参数(所以它是一个闭包).如果是这样,我如何在避免此问题的同时链接任务?

有一个更好的方法吗?

Luc*_*ski 2

Task.Delay(250).Wait()
Run Code Online (Sandbox Code Playgroud)

当您在尝试异步的代码中使用时,您知道自己做错了。Wait这是一个无所事事的浪费线程。

下面的内容会好得多:

lastTask = lastTask.ContinueWith(t =>
{
    // do some task
}).ContinueWith(t => Task.Delay(250)).Unwrap();
Run Code Online (Sandbox Code Playgroud)

ContinueWith返回 a Task<Task>,调用Unwrap将其转换为 a ,它将在内部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);
    }
}
Run Code Online (Sandbox Code Playgroud)

这是用 dotPeek 在“show me all the guts”模式下反编译的。

看这部分:

.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))))
Run Code Online (Sandbox Code Playgroud)

ContinueWith函数被赋予一个单例委托。因此,那里没有任何变量。

现在,有这个功能:

internal Task <DoSomeTask>b__2_1(Task t)
{
    return Task.Delay(250);
}
Run Code Online (Sandbox Code Playgroud)

这里t是对上一个任务的引用。注意到什么了吗?它从未被使用过。JIT 会将这个本地标记为无法访问,并且 GC 将能够清理它。启用优化后,JIT 将积极标记符合收集条件的本地变量,甚至在GC 收集实例this可以执行实例方法(如果所述实例方法未在剩下的代码中引用)执行。

m_parent现在,最后一件事是,类中有一个字段Task,这对您的场景不利。但只要你不使用TaskCreationOptions.AttachedToParent就应该没问题。您始终可以添加该DenyChildAttach标志以获得额外的安全性和自我记录。

这是处理这个问题的函数

internal static Task InternalCurrentIfAttached(TaskCreationOptions creationOptions)
{
    return (creationOptions & TaskCreationOptions.AttachedToParent) != 0 ? InternalCurrent : null;
}
Run Code Online (Sandbox Code Playgroud)

所以,你在这里应该是安全的。如果您想确定,请在长链上运行内存分析器,然后亲自查看。