【问题标题】:Read file while updating progress bar with coroutines in Unity在 Unity 中使用协程更新进度条时读取文件
【发布时间】:2018-10-29 21:38:30
【问题描述】:

我正在尝试在更新进度条时逐行读取文件(两个 GUI 纹理,其中一个宽度 (maxWidth * currentPercentage) 具有浮动扩展)。

我有两个实现:

    public static string ThreadedFileRead(string path, Action<float> percAction)
    {
        FileInfo fileInfo = new FileInfo(path);
        StringBuilder sb = new StringBuilder();

        float length = fileInfo.Length;
        int currentLength = 0;

        using (StreamReader sr = new StreamReader(path))
        {
            while (!sr.EndOfStream)
            {
                string str = sr.ReadLine();
                sb.AppendLine(str);

                // yield return str;

                percAction(currentLength / length);
                currentLength += str.Length;
                Interlocked.Add(ref currentLength, str.Length);
            }

            percAction(1f);

            return sb.ToString();
        }
    }

使用以下实现:

  // Inside a MonoBehaviour

  public void Start() 
  {
       string fileContents = "";
       StartCoroutine(LoadFileAsync(Application.dataPath + "/Data/file.txt", (s) => fileContents = s));
  }

  public IEnumerator LoadFileAsync(string path, Action<string> fin) 
  {
        string contents = "";

        lock (contents)
        {
            var task = Task.Factory.StartNew(() =>
            {
                contents = F.ThreadedFileRead(path, (f) => currentLoadProgress = f);
            });

            while (!task.IsCompleted)
                yield return new WaitForEndOfFrame();

            fin?.Invoke(contents);
        }
  }

但这会阻止当前的 GUI(我不知道为什么)。

我也用过这个:

    // Thanks to: https://stackoverflow.com/questions/41296957/wait-while-file-load-in-unity
    // Thanks to: https://stackoverflow.com/a/34378847/3286975
    [MustBeReviewed]
    public static IEnumerator LoadFileAsync(string pathOrUrl, Action<float> updatePerc, Action<string> finishedReading)
    {
        FileInfo fileInfo = new FileInfo(pathOrUrl);

        float length = fileInfo.Length;

        // Application.isEditor && ??? // Must review
        if (Path.IsPathRooted(pathOrUrl))
            pathOrUrl = "file:///" + pathOrUrl;

        /*

        using (var www = new UnityWebRequest(pathOrUrl))
        {
            www.downloadHandler = new DownloadHandlerBuffer();

            CityBenchmarkData.StartBenchmark(CityBenchmark.SendWebRequest);

            yield return www.SendWebRequest();

            CityBenchmarkData.StopBenchmark(CityBenchmark.SendWebRequest);

            while (!www.isDone)
            {
                // www.downloadProgress
                updatePerc?.Invoke(www.downloadedBytes / length); // currentLength / length
                yield return new WaitForEndOfFrame();
            }

            finishedReading?.Invoke(www.downloadHandler.text);
        }

         */

        using (var www = new WWW(pathOrUrl))
        {
            while (!www.isDone)
            {
                // www.downloadProgress
                updatePerc?.Invoke(www.bytesDownloaded / length); // currentLength / length
                yield return new WaitForEndOfFrame();
            }

            finishedReading?.Invoke(www.text);
        }
    }

使用以下实现:

  public IEnumerator LoadFileAsync(string path, Action<string> fin) 
  {
       yield return F.LoadFileAsync(path, (f) => currentLoadProgress = f, fin);
  }

我分享的最后一段代码有两部分:

  • 被注释的部分也阻塞了主线程。
  • 我使用的 WWW 类(将来会弃用)不会阻塞主线程,但它只会在进度条上显示两个步骤(如 25% 和 70%)。

我不知道为什么会发生这种情况,以及是否有更好的方法。

因此,欢迎对此提供任何帮助(指导)。

【问题讨论】:

  • “过时的部分(WWW 类)不会阻塞主线程” 抱歉,您的问题中没有 WWW 代码。您使用了 UnityWebRequest,它们是不同的。另外,你能告诉我你是怎么打电话给ThreadedFileRead的吗?也显示函数名和返回类型。
  • 是的,我正在使用using (var www = new WWW(pathOrUrl))。要查看我在哪里调用ThreadedFileRead,请查看第二个代码块(我编辑了我的问题)。

标签: c# unity3d stream coroutine


【解决方案1】:

被注释的部分也阻塞了主线程。

不是“阻塞”主线程。如果它阻塞了主线程,编辑器也会冻结,直到加载或下载完成。它只是在等待www.SendWebRequest() 完成,在等待期间,您项目中的其他脚本仍在正常运行,并且每一帧都在运行。

问题是有些人不明白www.isDone是怎么用的,什么时候用的。一个例子是this post,当人们发现这样的代码时,他们会遇到问题。

UnityWebRequest的两种使用方式:

1。下载或提出请求,然后在完成之前忘记它。当您不需要需要知道下载状态(例如 UnityWebRequest.downloadedBytesUnityWebRequest.uploadedBytesUnityWebRequest.downloadProgressUnityWebRequest.uploadProgress 值)时,您可以这样做。

为此,您需要使用 yield return 关键字生成 UnityWebRequest.SendWebRequest() 函数。它将等待UnityWebRequest.SendWebRequest() 函数,直到下载完成,但它不会阻止您的程序。 Update 函数和其他脚本中的代码应该仍在运行。该等待仅在您的 LoadFileAsync 协程函数中完成。

示例(注意使用yield return www.SendWebRequest() UnityWebRequest.isDone

using (var www = new UnityWebRequest(pathOrUrl))
{
    www.downloadHandler = new DownloadHandlerBuffer();
    yield return www.SendWebRequest();

    if (www.isHttpError || www.isNetworkError)
    {
        Debug.Log("Error while downloading data: " + www.error);
    }
    else
    {
        finishedReading(www.downloadHandler.text);
    }
}

2。下载或发出请求,然后等待每一帧,直到 UnityWebRequest.isDone 变为 true。当您需要了解下载状态时,您可以这样做,例如 UnityWebRequest.downloadedBytesUnityWebRequest.uploadedBytesUnityWebRequest.downloadProgressUnityWebRequest.uploadProgress 值,这些可以在等待 UnityWebRequest.isDone 时检查在循环中成为true

在这种情况下,您不能等待或放弃UnityWebRequest.SendWebRequest() 函数,因为放弃它将在那里等待,直到请求完成,从而无法检查下载状态。只需像普通函数一样调用UnityWebRequest.SendWebRequest() 函数,然后使用UnityWebRequest.isDonewhile 循环中等待。

等待帧是在循环中使用yield return nullyield return new WaitForEndOfFrame() 完成的,但建议使用yield return null,因为这不会创建GC。

示例(注意使用UnityWebRequest.isDone yield return www.SendWebRequest()

using (var www = new UnityWebRequest(pathOrUrl))
{
    www.downloadHandler = new DownloadHandlerBuffer();
    //Do NOT yield the SendWebRequest function when using www.isDone
    www.SendWebRequest();

    while (!www.isDone)
    {
        updatePerc.Invoke(www.downloadedBytes / length); // currentLength / length
        yield return null;
    }

    if (www.isHttpError || www.isNetworkError)
    {
        Debug.Log("Error while downloading data: " + www.error);
    }
    else
    {
        finishedReading(www.downloadHandler.text);
    }
}

您正在混合 #1#2。在您的情况下,您需要使用 #2。请注意,在这两种情况下,我在下载后和使用 if (www.isHttpError || www.isNetworkError) 加载的数据之前检查了错误。你必须这样做。


我使用的 WWW 类(将来会弃用)不会阻塞 主线程,但它只在进度条上显示两个步骤 (例如 25% 和 70%)。

如果这是真的,那么很可能在解决了我与UnityWebRequest 谈到的问题后,UnityWebRequest API 可能也会像 WWW API 一样给你 25% 和 70%。

您看到25%70% 的原因是因为文件非常小,所以Unity API 加载它的速度很快,它会跳过一些百分比值。这对于 Unity 的 API 来说是正常的。只需使用任何 C#System.IO API 来读取文件即可解决此问题。让您的 ThreadedFileRead 函数通过 Action 返回结果,就像您的 LoadFileAsync 函数一样。这将使线程更容易实现。

获取用于回调主线程的UnityThread 脚本。这样做是为了可以将回调返回的值与主线程上的 Unity API 一起使用。

Awake 函数中初始化线程回调脚本,然后使用ThreadPool.QueueUserWorkItemTask.Factory.StartNew 处理文件加载。要将值发送回主线程,请使用UnityThread.executeInUpdate 进行调用。

void Awake()
{
    UnityThread.initUnityThread();
}

public static void ThreadedFileRead(string path, Action<float> percAction, Action<string> finishedReading)
{
    /* Replace Task.Factory with ThreadPool when using .NET <= 3.5
     * 
     * ThreadPool.QueueUserWorkItem(state =>
     * 
     * */

    var task = Task.Factory.StartNew(() =>
    {
        FileInfo fileInfo = new FileInfo(path);
        StringBuilder sb = new StringBuilder();

        float length = fileInfo.Length;
        int currentLength = 0;

        using (StreamReader sr = new StreamReader(path))
        {
            while (!sr.EndOfStream)
            {
                string str = sr.ReadLine();
                sb.AppendLine(str);

                // yield return str;

                //Call on main Thread
                UnityThread.executeInUpdate(() =>
                 {
                     percAction(currentLength / length);
                 });

                currentLength += str.Length;
                //Interlocked.Add(ref currentLength, str.Length);
            }

            //Call on main Thread
            UnityThread.executeInUpdate(() =>
             {
                 finishedReading(sb.ToString());
             });
        }
    });
}

用法:

ThreadedFileRead(path, (percent) =>
{
    Debug.Log("Update: " + percent);
}, (result) =>
{
    Debug.Log("Done: " + result);
});

【讨论】:

  • 您尝试了解决方案吗? UnityWebRequest 或新的 ThreadedFileRead 函数有任何问题吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多