【问题标题】:Chaining tasks with delays有延迟的链式任务
【发布时间】:2016-08-27 17:34:43
【问题描述】:

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

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 将最后一个任务作为参数(所以它是关闭)。如果是这样,我怎样才能在避免这个问题的同时链接任务?

有没有更好的方法来做到这一点?

【问题讨论】:

  • 你能假设DoSomeTask()不会被并行调用吗?
  • @YacoubMassad 这不会影响他提出的具体问题,即使他是。
  • 你可以使用async/await吗?代码将简单易懂。

标签: c# task-parallel-library


【解决方案1】:

看看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】:
    Task.Delay(250).Wait()
    

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

    以下会更好:

    lastTask = lastTask.ContinueWith(t =>
    {
        // do some task
    }).ContinueWith(t => Task.Delay(250)).Unwrap();
    

    ContinueWith 返回一个Task<Task>Unwrap 调用将其转换为一个Task,当 inner 任务执行时将完成。

    现在,为了回答你的问题,我们来看看编译器生成了什么:

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

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

    看这部分:

    .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))))
    

    ContinueWith 函数被赋予了一个单例委托。所以,那里的任何变量都没有关闭。

    现在,有这个功能:

    internal Task <DoSomeTask>b__2_1(Task t)
    {
        return Task.Delay(250);
    }
    

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

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

    这是function which deals with that

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

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

    【讨论】:

      【解决方案3】:

      如果我做这样的事情,创建可能很长的任务链是旧任务会被处理

      任务不需要显式处置,因为它们不包含非托管资源。

      他们最终会不会因为 ContinueWith 将最后一个任务作为参数而永远存在(所以它是一个闭包)

      不是闭包。闭包是一种匿名方法,它在其主体中使用来自该匿名方法范围之外的变量。你没有这样做,所以你没有关闭它。但是,每个 Task 都有一个字段来跟踪其父对象,因此如果您使用此模式,托管的 Task 对象仍然可以访问。

      【讨论】:

      • 在他的场景中,父母应该为空,只要他避免TaskCreationOptions.AttachedToParent
      猜你喜欢
      • 2018-10-21
      • 2015-09-14
      • 2021-12-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多