【问题标题】:Delegate Closure with no memory allocation没有内存分配的委托闭包
【发布时间】:2017-04-23 00:50:28
【问题描述】:

我写了一个 Thread 帮助类,可以用来在 Unity 的主线程中执行一段代码。

这是功能蓝图:

public static void executeInUpdate(System.Action action)

完整的脚本真的很长,会使这篇文章变得不必要地冗长。你可以看到脚本助手类的其余部分here

然后我可以像这样使用另一个 Thread 的统一 API:

UnityThread.executeInUpdate(() =>
{
    transform.Rotate(new Vector3(0f, 90f, 0f));
});

问题是,当我使用在该委托之外声明的变量时,它会分配内存。上面的代码每帧分配 104 个字节。这是因为在该闭包中使用了 transform 变量。

现在这似乎没什么,但我每秒执行 60 次,我需要连接大约 6 个摄像头并在屏幕上显示图像。我不喜欢产生的垃圾量。

下面是我如何从相机下载图像并将其上传到 Unity 的示例。我每秒大约有 60 帧。 receiveVideoFrame() 函数在单独的线程上运行。它下载图像,将其发送到 Unity 的主 Thread,然后 Unity 将图像字节上传到 Texture2D。然后Texture2DRawImage 一起显示。由于UnityThread.executeInUpdate 而捕获闭包时会发生分配。

bool doneUploading = false;
byte[] videoBytes = new byte[25000];
public Texture2D videoDisplay;

void receiveVideoFrame()
{
    while (true)
    {
        //Download Video Frame 
        downloadVideoFrameFromNetwork(videoBytes);

        //Display Video Frame 
        UnityThread.executeInUpdate(() =>
        {
            //Upload the videobytes to Texture to display
            videoDisplay.LoadImage(videoBytes);
            doneUploading = true;
        });

        //Wait until video is done uploading to Texture/Displayed
        while (!doneUploading)
        {
            Thread.Sleep(1);
        }

        //Done uploading Texture. Now set to false for the next run
        doneUploading = false;

        //Repeat again
    }
}

如何使用闭包而不引起内存分配?

如果这不可能,还有其他方法吗?

我可以删除用于在主线程中执行代码的类,然后在主脚本中重新编写这些逻辑,但这会很麻烦而且很长。

【问题讨论】:

    标签: c# multithreading unity3d delegates closures


    【解决方案1】:

    经过数小时的实验,我发现如果将带有闭包的代码放在函数中,然后将其缓存到StartAwake 函数中的Action 变量一次,当您在 while 循环中调用闭包时,闭包内存分配将消失。

    另外,我将doneUploading 布尔变量设为volatile,以便Threads 都可以看到更改。我认为这里的 boolean 变量不需要 lock 关键字。

    private volatile bool doneUploading = false;
    
    byte[] videoBytes = new byte[25000];
    public Texture2D videoDisplay;
    
    Action chachedUploader;
    
    void Start()
    {
        chachedUploader = uploadToTexture;
    }
    
    void uploadToTexture()
    {
        UnityThread.executeInUpdate(() =>
        {
            //Upload the videobytes to Texture to display
            videoDisplay.LoadImage(videoBytes);
            doneUploading = true;
        });
    }
    
    //running in another Thread
    void receiveVideoFrame()
    {
        while (true)
        {
            //Download Video Frame 
            downloadVideoFrameFromNetwork(videoBytes);
    
            //Display Video Frame (Called on main thread)
            UnityThread.executeInUpdate(chachedUploader);
    
            //Wait until video is done uploading to Texture/Displayed
            while (!doneUploading)
            {
                Thread.Sleep(1);
            }
    
            //Done uploading Texture. Now set to false for the next run
            doneUploading = false;
    
            //Repeat again
        }
    }
    

    如果有人发现任何不好的地方,我愿意接受更多改进。虽然,我已经解决了我的问题。闭包不再分配内存了。

    【讨论】:

    • 您正在嵌套调用executeInUpdate。据我所知,您只需要缓存最里面的委托。如果您想真正参与其中,我认为您可以使用内存屏障而不是依赖volatile,但这可能需要更多考虑。
    • @RobH “你只需要缓存我所看到的最里面的委托” 有什么替代方案的例子吗?
    • 这样可以吗? gist.github.com/Rob89/3a1e33ceae0d4859a7b9b11d86a27113您可能仍然需要操作字段,但我认为您不需要。
    • @RobH 谢谢。我刚刚尝试过,它分配了 104 个字节,这是我试图解决的问题。通过将其缓存到Action,它根本不会分配。太奇怪了。
    • 我的意思不是lock,因为正如你所说,这太过分而且昂贵。我的意思是使用Thread.MemoryBarrier,但volatile 绝对没问题,而且更容易。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多