离开任务未被引用是否安全

时间:2012-02-29 15:55:36

标签: .net task-parallel-library task

如果我确定它没有抛出异常,那么保留任务未被引用是否安全?在收集任务之前,GC会等待任务完成吗?

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

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

PS。说实话,我认为在任务完成之前,TaskScheduller会保存对它的引用(在我的例子中,continuation任务也包含对tasks数组的引用)。因此GC无法从数组中收集延续任务和所有任务,直到所有任务完成。

1 个答案:

答案 0 :(得分:2)

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

上面用大写字母表示“BUT”意味着特定情况可能存在问题:如果您将一个Task实例保留在uscheduled状态并且松开所有对它的引用。

以下是具体示例(实际上我在这里发布问题时发生的事情)如果你在一些任务上继续执行,其中一个任务失败并且至少有一个未安排(其余的已完成)如果有任何)并且你没有对所有任务的引用,也没有保存对ContinueWhenAll返回的引用的引用,则下次收集垃圾时GC将收集所有这些引用。而在ContinueWhenAll中传递的失败任务将导致任务未观察到的异常。

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

  1. 刚刚调用了新的任务(...)(没有进一步调用Start 方法)
  2. 它是由TaskCompleteionSource创建的,但未设置为 完成,失败或取消。
  3. 从TPL的角度来看,这种行为看起来是一致的。仅仅因为你不应该提供永远不会被安排到ContinueWhenAll方法并永远等待的任务。所以实际上如果ContinueWhenAll中没有传递的其他任务都失败了,那么继续将永远不会发生而不是发生任务未观察到的异常。就是这样!