在更新进度条时,我试图逐行读取文件(两个GUI纹理,其宽度之一(最大宽度* 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);
}
我共享的最后一个代码包含两个部分:
我不知道为什么会这样,如果有更好的方法可以解决这个问题。
因此,欢迎您提供任何帮助(指导)。
答案 0 :(得分:1)
被注释的部分也会阻塞主线程。
不“阻塞”主线程。如果它阻塞了主线程,则编辑器也会冻结,直到完成加载或下载。它只是在等待www.SendWebRequest()
完成,而在等待时,项目中的其他脚本仍在每一帧都正常运行。
问题是某些人不了解www.isDone
的用法和使用时间。一个示例是this帖子,当人们找到这样的代码时,就会遇到问题。
使用UnityWebRequest
的两种方法:
1 。下载或提出请求,然后将其忘记,直到完成。当您不需要需要了解下载状态时,例如UnityWebRequest.downloadedBytes
,UnityWebRequest.uploadedBytes
和UnityWebRequest.downloadProgress
和UnityWebRequest.uploadProgress
要执行此操作,请使用UnityWebRequest.SendWebRequest()
关键字生成yield return
函数。它将等待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.downloadedBytes
,UnityWebRequest.uploadedBytes
和UnityWebRequest.downloadProgress
和UnityWebRequest.uploadProgress
,您可以执行此操作,并且可以在等待UnityWebRequest.isDone
成为true
循环时进行检查。
在这种情况下,您无法等待或产生UnityWebRequest.SendWebRequest()
函数,因为产生该函数会一直等待直到请求完成,从而无法检查下载状态。只需像普通函数一样调用UnityWebRequest.SendWebRequest()
函数,然后使用while
在UnityWebRequest.isDone
循环中进行等待。
等待帧是使用yield return null
或yield 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.QueueUserWorkItem
或Task.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);
});