【问题标题】:Is it safe to left task unreferenced不引用任务是否安全
【发布时间】:2012-02-29 15:55:35
【问题描述】:

如果我确定它不会引发异常,那么不引用任务是否安全? GC 会等到任务完成后再收集它吗?

这是我的方法的一个示例,它将一组任务转换为一个任务,该任务在所有任务完成时完成(被取消或失败)。我的应用程序因未观察到的任务异常而失败(通过在我使用任务的任何地方记录 Task.Id,我发现未观察到的任务是提供给此方法的任务,或者至少具有相同的 id)。我不知道为什么会发生这种情况,除了垃圾收集器收集从 Task.Factory.ContinueWhenAll 返回的任务,它在完成时没有等待,因此它也可能从未引用的数组中收集我的所有任务,如果至少有一个失败的任务它将导致任务未观察到的异常。听起来很疯狂,但我没有看到对发生的事情的另一种解释。那么有可能吗?

        public static Task ToWhenAllTask(this Task[] tasks, bool cancelIfAnyCanceled = true)
    {
        if (tasks != null && tasks.Length == 0)
            throw new ArgumentException();

        var tcs = new TaskCompletionSource<object>();

        Task.Factory.ContinueWhenAll(tasks, ts => {
            try
            {
                List<Exception> errors = null;
                bool canceled = false;

                foreach (Task task in ts)
                {
                    AggregateException ex = task.Exception;

                    if (ex != null)
                    {
                        if (errors == null)
                            errors = new List<Exception>();

                        errors.Add(ex.Flatten());
                    }

                    if (task.IsCanceled)
                        canceled = true;
                }

                if (errors != null)
                    tcs.TrySetException(errors);
                else if (cancelIfAnyCanceled && canceled)
                    tcs.TrySetCanceled();
                else
                    tcs.TrySetResult(null);
            }
            catch(Exception ex)
            {

                // there is nothing to fail in this method but just in case
                tcs.TrySetException(ex);
            }

        }, TaskContinuationOptions.ExecuteSynchronously);

        return tcs.Task;
    }

附言。老实说,我认为在任务完成之前 TaskScheduler 持有对它的引用(在我的情况下,延续任务也持有对任务数组的引用)。所以 GC 无法从数组中收集延续任务和所有任务,直到它们全部完成。

【问题讨论】:

    标签: .net task-parallel-library task


    【解决方案1】:

    我终于找到了问题的原因。我在第一行发布的问题的直接答案是 - 是的,如果您确定任务不会因错误而失败,那么不引用任务是安全的。是的,任务在完成之前不会被 GC 收集,但只有在它们开始时(即它们被安排在 TaskScheduler 上)

    上面的大写字母“BUT”表示在非常特殊的情况下可能会出现问题:如果您将 Task 的实例留在 uscheduled 状态并丢失对它的所有引用。

    这是一个具体示例(实际上是我在此处发布问题时发生的情况),如果您对几个任务执行 ContinueWhenAll,其中一个任务因错误而失败并且至少一个未安排(其余任务已完成)如果有的话)并且您丢失了对所有这些任务的引用,并且也不保存对 ContinueWhenAll 返回的引用的引用,那么所有这些任务都将在 GC 下次收集垃圾时收集。而在 ContinueWhenAll 中传递的那些失败的任务将导致 Task Unobserved Exception。

    上面的未计划任务意味着它是通过以下任何一种方式创建的:

    1. 刚刚调用了 new Task(...)(没有进一步调用 Start 方法)
    2. 它是由 TaskCompletionSource 创建的,但未设置为 完成、失败状态或取消。

    从 TPL 的角度来看,这种行为看起来是一致的。仅仅因为您不应该提供永远不会被安排到 ContinueWhenAll 方法并永远等待的任务。因此,实际上,如果在 ContinueWhenAll 中传递的其他任务都没有失败,则将永远不会发生延续,而不是发生 Task Unobserved Exception。就是这样!

    【讨论】:

      猜你喜欢
      • 2019-06-05
      • 2010-10-01
      • 2012-07-30
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多