如果我确定它没有抛出异常,那么保留任务未被引用是否安全?在收集任务之前,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无法从数组中收集延续任务和所有任务,直到所有任务完成。
答案 0 :(得分:2)
最后我找到了解决问题的原因。在第一行发布的问题的直接答案是 - 是的,如果您确定它不会因错误而失败,那么保留任务未被引用是安全的。是的,GC不会收集任务,直到他们完成但是只有当他们开始时(即他们被安排在TaskScheduler上)
上面用大写字母表示“BUT”意味着特定情况可能存在问题:如果您将一个Task实例保留在uscheduled状态并且松开所有对它的引用。
以下是具体示例(实际上我在这里发布问题时发生的事情)如果你在一些任务上继续执行,其中一个任务失败并且至少有一个未安排(其余的已完成)如果有任何)并且你没有对所有任务的引用,也没有保存对ContinueWhenAll返回的引用的引用,则下次收集垃圾时GC将收集所有这些引用。而在ContinueWhenAll中传递的失败任务将导致任务未观察到的异常。
上面未安排的任务意味着它是通过以下任何一种方式创建的:
从TPL的角度来看,这种行为看起来是一致的。仅仅因为你不应该提供永远不会被安排到ContinueWhenAll方法并永远等待的任务。所以实际上如果ContinueWhenAll中没有传递的其他任务都失败了,那么继续将永远不会发生而不是发生任务未观察到的异常。就是这样!