在Unity中使用协程更新进度条时读取文件
Read file while updating progress bar with coroutines in Unity
我试图在更新进度条时逐行读取文件(两个 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:
// Thanks to:
[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);
}
我分享的最后一个代码有两部分:
- 注释部分也阻塞了主线程。
- 我用的WWWclass(以后会弃用)不会阻塞主线程,但是进度条上只显示两个步骤(比如25%和70%)。
我不知道为什么会这样,也不知道是否有更好的方法。
因此,欢迎为此提供任何帮助(指导)。
The commented part blocks also the main thread.
不是"blocking"主线程。如果它阻塞了主线程,编辑器也会冻结,直到加载或下载完成。它只是在等待 www.SendWebRequest()
完成,在等待的同时,您项目中的其他脚本仍在正常 运行 每一帧运行。
问题是有些人不明白www.isDone
是怎么用的,什么时候用。一个例子是 this post,当人们找到这样的代码时,他们 运行 会提出问题。
两种使用方式UnityWebRequest
:
1。下载或提出请求,然后忘记它,直到完成。当您 不需要 需要知道下载状态时,例如 UnityWebRequest.downloadedBytes
、UnityWebRequest.uploadedBytes
和 UnityWebRequest.downloadProgress
和 [=20],您可以执行此操作=] 值。
为此,您需要使用 yield return
关键字生成 UnityWebRequest.SendWebRequest()
函数。它将等待 UnityWebRequest.SendWebRequest()
函数,直到下载完成,但它不会阻止您的程序。 Update
函数和其他脚本中的代码应该仍然是 运行ning。该等待仅在您的 LoadFileAsync
协程函数中完成。
示例(注意使用 yield return www.SendWebRequest()
和 no 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()
函数,然后使用 UnityWebRequest.isDone
.
在 while
循环中等待
等待帧是在 yield return null
或 yield return new WaitForEndOfFrame()
循环中完成的,但建议使用 yield return null
,因为这不会创建 GC。
示例(注意使用 UnityWebRequest.isDone
和 no 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)
使用加载的数据之前检查了错误。你必须这样做。
The WWW class I used (it will be deprecated in a future) doesn't block
the main thread, but it only displays two steps on the progress bar
(like 25% and 70%).
如果那是真的,那么很可能在解决我与 UnityWebRequest
谈到的问题后,UnityWebRequest
API 可能会给你 25% 和 70% 的赞WWW API 也是。
您看到 25%
和 70%
的原因是因为文件非常小,所以 Unity API 加载速度很快,跳过了一些百分比值。这对于 Unity 的 API 是正常的。只需使用任何 C# System.IO
API 读取文件即可解决此问题。使您的 ThreadedFileRead
函数 return 通过 Action
的结果就像您的 LoadFileAsync
函数一样。这将使线程的实现变得更容易。
获取用于回调主线程的UnityThread
脚本。您这样做是为了可以在主线程上使用通过 Unity API 从回调中编辑的值 return。
在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);
});
我试图在更新进度条时逐行读取文件(两个 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:
// Thanks to:
[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);
}
我分享的最后一个代码有两部分:
- 注释部分也阻塞了主线程。
- 我用的WWWclass(以后会弃用)不会阻塞主线程,但是进度条上只显示两个步骤(比如25%和70%)。
我不知道为什么会这样,也不知道是否有更好的方法。
因此,欢迎为此提供任何帮助(指导)。
The commented part blocks also the main thread.
不是"blocking"主线程。如果它阻塞了主线程,编辑器也会冻结,直到加载或下载完成。它只是在等待 www.SendWebRequest()
完成,在等待的同时,您项目中的其他脚本仍在正常 运行 每一帧运行。
问题是有些人不明白www.isDone
是怎么用的,什么时候用。一个例子是 this post,当人们找到这样的代码时,他们 运行 会提出问题。
两种使用方式UnityWebRequest
:
1。下载或提出请求,然后忘记它,直到完成。当您 不需要 需要知道下载状态时,例如 UnityWebRequest.downloadedBytes
、UnityWebRequest.uploadedBytes
和 UnityWebRequest.downloadProgress
和 [=20],您可以执行此操作=] 值。
为此,您需要使用 yield return
关键字生成 UnityWebRequest.SendWebRequest()
函数。它将等待 UnityWebRequest.SendWebRequest()
函数,直到下载完成,但它不会阻止您的程序。 Update
函数和其他脚本中的代码应该仍然是 运行ning。该等待仅在您的 LoadFileAsync
协程函数中完成。
示例(注意使用 yield return www.SendWebRequest()
和 no 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()
函数,然后使用 UnityWebRequest.isDone
.
while
循环中等待
等待帧是在 yield return null
或 yield return new WaitForEndOfFrame()
循环中完成的,但建议使用 yield return null
,因为这不会创建 GC。
示例(注意使用 UnityWebRequest.isDone
和 no 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)
使用加载的数据之前检查了错误。你必须这样做。
The WWW class I used (it will be deprecated in a future) doesn't block the main thread, but it only displays two steps on the progress bar (like 25% and 70%).
如果那是真的,那么很可能在解决我与 UnityWebRequest
谈到的问题后,UnityWebRequest
API 可能会给你 25% 和 70% 的赞WWW API 也是。
您看到 25%
和 70%
的原因是因为文件非常小,所以 Unity API 加载速度很快,跳过了一些百分比值。这对于 Unity 的 API 是正常的。只需使用任何 C# System.IO
API 读取文件即可解决此问题。使您的 ThreadedFileRead
函数 return 通过 Action
的结果就像您的 LoadFileAsync
函数一样。这将使线程的实现变得更容易。
获取用于回调主线程的UnityThread
脚本。您这样做是为了可以在主线程上使用通过 Unity API 从回调中编辑的值 return。
在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);
});