这里我有一个方法
ImportFbxAsync();
,首先调用BuildMaterialBankAsync();
并保持其异步运行,而无需await
,因为这需要时间,然后在另一个任务中使用BuildScene()
,然后我等待BuildMaterialBankAsync() ;
的结果,然后继续我的工作;BuildMaterialBankAsync();
将创建永远不会被释放的内存泄漏,即使该方法在 if (fbxPath == null)
或 if (ShowImportOptions(importOptions))
时提前返回/停止,并且泄漏将在应用程序的整个生命周期内持续
public class FbxImport
{
public static async Task ImportFbxAsync(StudioViewModel studioView)
{
studioView.IsBackgroundLoading = true;
CancellationTokenSource cancellationTokenSource = new();
// Build material bank asynchronously
var materialBankTask = BuildMaterialBankAsync(studioView, cancellationTokenSource.Token);
var fbxPath = GetFbxFilePath(studioView);
if (fbxPath == null)
{
Logger.Log(0, "Import canceled.", "Import canceled");
studioView.IsBackgroundLoading = false;
// Cancel the materialBankTask
cancellationTokenSource.Cancel();
return;
}
Logger.Log(17, fbxPath, "Importing...");
// No Memory Leak Detected From BuildScene ATM !
var fBXScene = await BuildScene(studioView, fbxPath);
// Wait For material bank From BuildMaterialBankAsync Task
var materialBankResult = await materialBankTask;
try
{
if (fBXScene == null)
{
Logger.Log(18, "Fbx Import Failed ", "Fbx Import Failed ", false);
return;
}
var importOptions = CreateImportOptions(
studioView.TargetedGame,
fBXScene,
materialBankResult
);
//Possible to Cancel Here
if (ShowImportOptions(importOptions))
{
await ProcessBones(studioView, importOptions, fBXScene);
await ProcessMeshes(studioView, importOptions, fBXScene);
await ProcessDummies(studioView, importOptions, fBXScene);
Logger.Log(19, $"{fbxPath} \n imported successfully", " Successful Import.");
}
else
{
Logger.Log(0, "Import canceled.", "Import Canceled");
}
}
catch (Exception ex)
{
Logger.Log(0, ex.ToString(), "Import Faild", false);
}
finally
{
fBXScene?.Dispose();
materialBankResult?.Dispose();
materialBankTask?.Dispose();
studioView.IsBackgroundLoading = false;
}
}
public static async Task<MaterialInfoBank> BuildMaterialBankAsync(
StudioViewModel studioView,
CancellationToken cancellationToken
)
{
var filePath = $"IncludeResources\\MaterialBanks\\Bank{studioView.TargetedGame}.xml";
return await Task.Run(
() => MaterialInfoBank.ReadFromXML(filePath),
cancellationToken
);
}
}
有办法解决这个问题吗?或了解如何操作的指南?
另外,我不拥有
MaterialInfoBank
对象类,我不知道它是如何工作的,但我知道它读取一个 xml 文件并将其解析为三个字典
public Dictionary<string, MaterialDef> MaterialDefs = new Dictionary<string, MaterialDef>();
public Dictionary<string, XmlStructDef> GXItemStructs = new Dictionary<string, XmlStructDef>();
public Dictionary<string, List<byte[]>> DefaultGXItemDataExamples = new Dictionary<string, List<byte[]>>();
所以我尝试添加处置模式IDisposable
>,但它不起作用,泄漏仍然继续
protected virtual void Dispose(bool disposing)
{
if (!disposedValue)
{
if (disposing)
{
// TODO: dispose managed state (managed objects)
MaterialDefs.Clear();
GXItemStructs.Clear();
DefaultGXItemDataExamples.Clear();
MaterialDefs = null;
GXItemStructs = null;
DefaultGXItemDataExamples = null;
}
// TODO: free unmanaged resources (unmanaged objects) and override finalizer
// TODO: set large fields to null
disposedValue = true;
}
}
// // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
// ~FLVER2MaterialInfoBank()
// {
// // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
// Dispose(disposing: false);
// }
public void Dispose()
{
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
即使该方法在 if (fbxPath == null) 或 if (ShowImportOptions(importOptions)) 时提前返回/停止
不是完整的答案,但在这里强调一些误解/潜在的问题。
C# 的
Task
只支持 合作取消,如果 MaterialInfoBank.ReadFromXML
那么 Task.Run<TResult>(Func<Task<TResult>>, CancellationToken)
仅在工作尚未开始时才会被取消:
cancellationToken
CancellationToken
取消令牌,可用于取消尚未开始的工作。不会将Run<TResult>(Func<Task<TResult>>, CancellationToken)
传递给行动。cancellationToken
这通常不适合你,所以
MaterialInfoBank.ReadFromXML(filePath)
几乎总是会运行完成(除非线程池非常非常繁忙并且尚未开始任务)。如果您有需要处置的资源,那么您需要正确处理取消情况。至少像下面这样:
public static async Task<MaterialInfoBank> BuildMaterialBankAsync(
StudioViewModel studioView,
CancellationToken cancellationToken
)
{
var filePath = $"IncludeResources\\MaterialBanks\\Bank{studioView.TargetedGame}.xml";
return await Task.Run(
() =>
{
var result = MaterialInfoBank.ReadFromXML(filePath);
if (cancellationToken.IsCancellationRequested)
{
result.Dispose();
}
return result;
},
cancellationToken
);
}
但最好在
if (fbxPath == null)
检查后开始任务。
对于潜在的内存泄漏 - 首先您需要确定它确实是泄漏(即,当多次调用潜在泄漏的代码时,内存不断增长并且没有完全释放)。如果您已验证这是泄漏(而不是代码在整个应用程序生命周期中初始化和使用的某些资源),那么您将需要分析泄漏的内容(通过比较内存快照 - 例如与 dotMemory 或通过 dumps),然后尝试找到分配这些的代码。如果没有完整的最小可重现示例,我们只能猜测。
附注
通常在 .NET 4.5 之后不需要处置
Task
- 请参阅我需要处置任务吗?,因此可以删除 materialBankTask?.Dispose();
。