[英]Coroutine is blocking main thread on save image
I have a coroutine that takes a screenshot and saves to the file system. 我有一个协程,它需要截图并保存到文件系统。 But when the coroutine runs the game freezes momentarily and the event onFinishedScreenShot
fires prematurely. 但是,当协程运行时,游戏会立即冻结,并且event onFinishedScreenShot
会过早触发。
I thought that Coroutines were "on a background thread". 我以为协程“在后台线程上”。
Is there a way to adjust my code save a screenshot without blocking the main thread and have the event onFinishedScreenShot
fire AFTER the write file is finished? 有没有一种方法可以调整我的代码以保存屏幕截图而不阻塞主线程,并且在写入文件完成后 event onFinishedScreenShot
? Or is there a better way of handling saving screenshots for games? 还是有更好的方法来保存游戏的屏幕截图?
MySingleton.cs 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 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;
}
}
Unity coroutines and "threading" are two entirely different features. Unity协程和“线程”是两个完全不同的功能。 Coroutines are a way of managing when your code executes but have nothing to do with threads. 协程是一种管理代码何时执行但与线程无关的方法。 Think of the two like this: 像这样想两个:
Coroutines: "Do X, wait for Y to be done, then do Z." 协程: “执行X,等待Y完成,然后执行Z。”
Threads: "Do X and Y concurrently, then do Z." 线程: “同时执行X和Y,然后执行Z”。
If you want to open up your own threads, C# has the ability to do that via System.Threading
but you'll need to be careful; 如果您想打开自己的线程,C#可以通过System.Threading
来做到这一点,但是您需要小心; almost 100% of Unity APIs (anything coming from the UnityEngine
namespace) will assert when they aren't run on the main thread. 当它们不在主线程上运行时,将断言几乎100%的Unity API(来自UnityEngine
名称空间的任何东西)。 You'll need to get your raw byte data, open a new thread, then write the file: 您需要获取原始字节数据,打开一个新线程,然后写入文件:
...
byte[] bytes = areaImage.EncodeToPNG();
// Start a new thread here
File.WriteAllBytes(filename, bytes);
if (onFinishedScreenShot != null) {
onFinishedScreenShot(filename);
}
For your last question about the event firing: File.WriteAllBytes
is a synchronous call so your event is absolutely being fired after the file is being written (unless you fire that event from anywhere else not listed in the question). 关于事件触发的最后一个问题: File.WriteAllBytes
是一个同步调用,因此在写入文件后绝对会触发事件(除非您从问题中未列出的其他任何地方触发该事件)。
Maybe it's because you have a circular reference in your script. 可能是因为您的脚本中有循环引用。
MySingleton.onFinishedScreenShot += LoadTexture;
You are registering the handler in the handler function. 您正在处理程序函数中注册处理程序。 Try doing this in the Start()-function. 尝试在Start()函数中执行此操作。 This could fix the issue. 这可以解决问题。
This has nothing to do with coroutine. 这与协程无关。 Call the File.WriteAllBytes
function in another Thread with ThreadPool.QueueUserWorkItem
. 使用ThreadPool.QueueUserWorkItem
在另一个线程中调用File.WriteAllBytes
函数。 First, get UnityThread
class from this post which makes it easier to call a function on the main Thread
from another Thread
. 首先,从本文中获取UnityThread
类, 这使得从另一个Thread
调用主Thread
上的函数更加容易。 Below is what your code is supposed to look like. 下面是您的代码应该是什么样的。
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);
}
});
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.