协程在保存图像时阻塞主线程
Coroutine is blocking main thread on save image
我有一个协程可以截图并保存到文件系统。但是当协程运行时,游戏会暂时冻结并且 event onFinishedScreenShot
过早触发。
我认为协程是 "on a background thread"。
有没有办法调整我的代码保存屏幕截图而不阻塞主线程并让 event onFinishedScreenShot
触发 AFTER 写入文件完成?或者有更好的方法来处理游戏的保存屏幕截图吗?
MySingleton.cs
public event Action<string> onFinishedScreenShot;
public IEnumerator TakeScreenshot(string filename) {
Texture2D areaImage = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
yield return new WaitForEndOfFrame();
areaImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
areaImage.Apply();
byte[] bytes = areaImage.EncodeToPNG();
File.WriteAllBytes(filename, bytes);
if (onFinishedScreenShot != null) {
onFinishedScreenShot(filename);
}
}
AnotherClass.cs
private void Start() {
LoadTexture(someFileName);
}
public void SomeMethodInAnotherClass() {
StartCoroutine(MySingleton.TakeScreenshot(someFileName));
}
public void LoadTexture(string filename) {
if (!filename.Equals("NOT SET") ) {
byte[] bytes = File.ReadAllBytes(GetSaveFilename(filename));
Texture2D texture = new Texture2D(500, 500, TextureFormat.DXT5, false);
texture.filterMode = FilterMode.Trilinear;
texture.LoadImage(bytes);
RectTransform rt = locationSnapShot.rectTransform;
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, rt.rect.width, rt.rect.height), new Vector2(0.5f, 0.0f), 16.0f);
locationSnapShot.sprite = sprite;
Debug.Log("Removing event listener.");
MySingleton.onFinishedScreenShot -= LoadTexture;
} else {
Debug.Log("No screen shot. Listening for a change.");
MySingleton.onFinishedScreenShot += LoadTexture;
}
}
可能是因为您的脚本中存在循环引用。
MySingleton.onFinishedScreenShot += LoadTexture;
您正在处理程序函数中注册处理程序。
尝试在 Start() 函数中执行此操作。
这可以解决问题。
Unity 协程和 "threading" 是两个完全不同的特性。协程是一种管理何时 代码执行但与线程无关的方式。两个这样想:
协程:"Do X, wait for Y to be done, then do Z."
话题:"Do X and Y concurrently, then do Z."
如果您想打开自己的线程,C# 可以通过 System.Threading
实现,但您需要小心;几乎 100% 的 Unity API(任何来自 UnityEngine
命名空间的东西)都会在主线程上 不 运行 时断言。您需要获取原始字节数据,打开一个新线程,然后写入文件:
...
byte[] bytes = areaImage.EncodeToPNG();
// Start a new thread here
File.WriteAllBytes(filename, bytes);
if (onFinishedScreenShot != null) {
onFinishedScreenShot(filename);
}
关于事件触发的最后一个问题:File.WriteAllBytes
是一个同步调用,因此您的事件绝对会被触发 在 文件被写入之后(除非您触发该事件来自问题中未列出的其他任何地方。
这与协程无关。使用 ThreadPool.QueueUserWorkItem
在另一个线程中调用 File.WriteAllBytes
函数。首先,从 this post 获取 UnityThread
class 这使得从另一个 Thread
调用主 Thread
上的函数更容易。以下是您的代码应该是什么样子。
public event Action<string> onFinishedScreenShot;
//Data to pass to the Thread function
public class SaveData
{
public string fileName;
public byte[] imgByteArray;
}
void Awake()
{
//Enable the Thread callback API
UnityThread.initUnityThread();
}
public IEnumerator TakeScreenshot(string filename)
{
Texture2D areaImage = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
yield return new WaitForEndOfFrame();
areaImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
areaImage.Apply();
SaveData saveData = new SaveData();
saveData.fileName = filename;
saveData.imgByteArray = areaImage.EncodeToPNG();
//Save on a new Thread
WriteAllBytes(saveData);
}
void WriteAllBytes(SaveData saveData)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(saveFile));
}
private void saveFile(object state)
{
SaveData saveData = (SaveData)state;
File.WriteAllBytes(saveData.fileName, saveData.imgByteArray);
//Invoke the event on the MainThread
UnityThread.executeInUpdate(() =>
{
if (onFinishedScreenShot != null)
{
onFinishedScreenShot(filename);
}
});
}
我有一个协程可以截图并保存到文件系统。但是当协程运行时,游戏会暂时冻结并且 event onFinishedScreenShot
过早触发。
我认为协程是 "on a background thread"。
有没有办法调整我的代码保存屏幕截图而不阻塞主线程并让 event onFinishedScreenShot
触发 AFTER 写入文件完成?或者有更好的方法来处理游戏的保存屏幕截图吗?
MySingleton.cs
public event Action<string> onFinishedScreenShot;
public IEnumerator TakeScreenshot(string filename) {
Texture2D areaImage = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
yield return new WaitForEndOfFrame();
areaImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
areaImage.Apply();
byte[] bytes = areaImage.EncodeToPNG();
File.WriteAllBytes(filename, bytes);
if (onFinishedScreenShot != null) {
onFinishedScreenShot(filename);
}
}
AnotherClass.cs
private void Start() {
LoadTexture(someFileName);
}
public void SomeMethodInAnotherClass() {
StartCoroutine(MySingleton.TakeScreenshot(someFileName));
}
public void LoadTexture(string filename) {
if (!filename.Equals("NOT SET") ) {
byte[] bytes = File.ReadAllBytes(GetSaveFilename(filename));
Texture2D texture = new Texture2D(500, 500, TextureFormat.DXT5, false);
texture.filterMode = FilterMode.Trilinear;
texture.LoadImage(bytes);
RectTransform rt = locationSnapShot.rectTransform;
Sprite sprite = Sprite.Create(texture, new Rect(0, 0, rt.rect.width, rt.rect.height), new Vector2(0.5f, 0.0f), 16.0f);
locationSnapShot.sprite = sprite;
Debug.Log("Removing event listener.");
MySingleton.onFinishedScreenShot -= LoadTexture;
} else {
Debug.Log("No screen shot. Listening for a change.");
MySingleton.onFinishedScreenShot += LoadTexture;
}
}
可能是因为您的脚本中存在循环引用。
MySingleton.onFinishedScreenShot += LoadTexture;
您正在处理程序函数中注册处理程序。 尝试在 Start() 函数中执行此操作。 这可以解决问题。
Unity 协程和 "threading" 是两个完全不同的特性。协程是一种管理何时 代码执行但与线程无关的方式。两个这样想:
协程:"Do X, wait for Y to be done, then do Z."
话题:"Do X and Y concurrently, then do Z."
如果您想打开自己的线程,C# 可以通过 System.Threading
实现,但您需要小心;几乎 100% 的 Unity API(任何来自 UnityEngine
命名空间的东西)都会在主线程上 不 运行 时断言。您需要获取原始字节数据,打开一个新线程,然后写入文件:
...
byte[] bytes = areaImage.EncodeToPNG();
// Start a new thread here
File.WriteAllBytes(filename, bytes);
if (onFinishedScreenShot != null) {
onFinishedScreenShot(filename);
}
关于事件触发的最后一个问题:File.WriteAllBytes
是一个同步调用,因此您的事件绝对会被触发 在 文件被写入之后(除非您触发该事件来自问题中未列出的其他任何地方。
这与协程无关。使用 ThreadPool.QueueUserWorkItem
在另一个线程中调用 File.WriteAllBytes
函数。首先,从 this post 获取 UnityThread
class 这使得从另一个 Thread
调用主 Thread
上的函数更容易。以下是您的代码应该是什么样子。
public event Action<string> onFinishedScreenShot;
//Data to pass to the Thread function
public class SaveData
{
public string fileName;
public byte[] imgByteArray;
}
void Awake()
{
//Enable the Thread callback API
UnityThread.initUnityThread();
}
public IEnumerator TakeScreenshot(string filename)
{
Texture2D areaImage = new Texture2D(Screen.width, Screen.height, TextureFormat.RGB24, false);
yield return new WaitForEndOfFrame();
areaImage.ReadPixels(new Rect(0, 0, Screen.width, Screen.height), 0, 0, false);
areaImage.Apply();
SaveData saveData = new SaveData();
saveData.fileName = filename;
saveData.imgByteArray = areaImage.EncodeToPNG();
//Save on a new Thread
WriteAllBytes(saveData);
}
void WriteAllBytes(SaveData saveData)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(saveFile));
}
private void saveFile(object state)
{
SaveData saveData = (SaveData)state;
File.WriteAllBytes(saveData.fileName, saveData.imgByteArray);
//Invoke the event on the MainThread
UnityThread.executeInUpdate(() =>
{
if (onFinishedScreenShot != null)
{
onFinishedScreenShot(filename);
}
});
}