异步任务造成内存泄漏

问题描述 投票:0回答:1

这里我有一个方法

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

Rider Memory Profile

Rider Memory Profile Before Calling ImportFbxAsync

Rider Memory Profile After Calling ImportFbxAsync

有办法解决这个问题吗?或了解如何操作的指南?

另外,我不拥有

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);
   }
c# memory-leaks .net-8.0
1个回答
0
投票

即使该方法在 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();

© www.soinside.com 2019 - 2024. All rights reserved.