异步读取图像文件

4

我想要异步地读取一个图像文件,尝试使用C#中的async/await实现。但是我仍然注意到这种实现存在显着的延迟。使用分析器,我非常有信心认为是FileStream.ReadAsync()方法需要很长时间才能完成,而不是以异步方式完成并停止游戏更新。

我发现这种方式比简单地使用File.ReadAllBytes方法更耗时,这很奇怪。我不能使用那个方法,因为它仍会导致一些延迟,我希望没有帧速率的停顿。

下面是一些代码:

// Static class for reading the image.
class AsyncImageReader
{
    public static async Task<byte[]> ReadImageAsync(string filePath)
        {
            byte[] imageBytes;
            didFinishReading = false;
            using (FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true))
            {
                imageBytes = new byte[fileStream.Length];
                await fileStream.ReadAsync(imageBytes, 0, (int) fileStream.Length);

            }

            didFinishReading = true;

            return imageBytes;
        }
 }

    // Calling function that is called by the OnCapturedPhotoToDisk event. When this is called the game stops updating completely rather than 
    public void AddImage(string filePath)
    {
        Task<byte[]> readImageTask = AsyncImageReader.ReadImageAsync(filePath);
        byte [] imageBytes = await readImageTask;
        // other things that use the bytes....
    }

1
你的代码无法编译,因为你的AddImage方法应该是异步的。 - Muhammad Touseef
你的文件有多大? - Poul Bak
1
请注意,async 方法总是在调用线程上运行,直到实际达到 await 语句(实际意义是它受到 IO 限制)。同时请注意,当此 await 完成后,工作将返回到相同的(调用)线程。这可能理论上是延迟的原因。 - Martin Zikmund
@touseefbsb 我知道,这是IDE告诉我的。这只是代码的副本。 - user14492
@PoulBak 不是文件太大,而是CPU太差。正在尝试使用Hololens工作。 - user14492
显示剩余3条评论
1个回答

0

你不需要使用Task来异步加载图片。你可以使用Unity的UnityWebRequestDownloadHandlerTexture API来加载。由于UnityWebRequestTexture.GetTexture会自动为你设置DownloadHandlerTexture,因此这个过程变得更加简单。它在后台另一个线程中完成,但你可以使用协程来控制它。

public RawImage image;

void Start()
{
    StartCoroutine(LoadTexture());
}

IEnumerator LoadTexture()
{
    string filePath = "";//.....
    UnityWebRequest uwr = UnityWebRequestTexture.GetTexture(filePath);
    yield return uwr.SendWebRequest();

    if (uwr.isNetworkError || uwr.isHttpError)
    {
        Debug.Log(uwr.error);
    }
    else
    {
        Texture texture = ((DownloadHandlerTexture)uwr.downloadHandler).texture;
        image.texture = texture;
    }
}

你也可以使用 Thread API 在另一个线程中加载文件。更好的方法是使用 ThreadPool API。任何一个都应该可以工作。一旦你加载了图像字节数组,为了将字节数组加载到 Texture2D 中,你必须在主线程中执行,因为你不能在主线程中使用 Unity 的 API。

当你完成加载图像字节时,如果想要从另一个线程中使用 Unity 的 API,你需要一个包装器来实现。下面的示例是使用 C# 的 ThreadPool 和我的 UnityThread API 来简化从另一个线程中使用 Unity 的 API。你可以从这里获取 UnityThread

public RawImage image;

void Awake()
{
    //Enable Callback on the main Thread
    UnityThread.initUnityThread();
}

void ReadImageAsync()
{
    string filePath = "";//.....

    //Use ThreadPool to avoid freezing
    ThreadPool.QueueUserWorkItem(delegate
    {
        bool success = false;

        byte[] imageBytes;
        FileStream fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, true);

        try
        {
            int length = (int)fileStream.Length;
            imageBytes = new byte[length];
            int count;
            int sum = 0;

            // read until Read method returns 0
            while ((count = fileStream.Read(imageBytes, sum, length - sum)) > 0)
                sum += count;

            success = true;
        }
        finally
        {
            fileStream.Close();
        }

        //Create Texture2D from the imageBytes in the main Thread if file was read successfully
        if (success)
        {
            UnityThread.executeInUpdate(() =>
            {
                Texture2D tex = new Texture2D(2, 2);
                tex.LoadImage(imageBytes);
            });
        }
    });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接