Coroutine阻止保存图像上的主线程

时间:2017-11-09 15:32:39

标签: c# unity3d coroutine system.io.file

我有一个协程,它会截取并保存到文件系统。但是当协同程序运行时,游戏会暂时冻结,而event onFinishedScreenShot会过早闪现。

我认为Coroutines是“在后台线程”。

有没有办法调整我的代码保存屏幕截图而不会阻止主线程并让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;
    }
}

3 个答案:

答案 0 :(得分:2)

Unity协程和“线程”是两个完全不同的功能。协程是一种在代码执行时管理但与线程无关的方法。想想这两个:

Coroutines:“做X,等待Y完成,然后做Z.”

线程:“同时执行X和Y,然后执行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是一个同步调用,所以在写入文件之后,你的事件绝对被激活(除非你从其他任何地方触发该事件)没有列在问题中。)

答案 1 :(得分:0)

也许是因为你的剧本中有一个循环引用。

MySingleton.onFinishedScreenShot += LoadTexture;

您正在处理程序函数中注册处理程序。 尝试在Start() - 函数中执行此操作。 这可以解决问题。

答案 2 :(得分:0)

这与协同程序无关。使用File.WriteAllBytes在另一个主题中调用ThreadPool.QueueUserWorkItem函数。首先,从this帖子中获取UnityThread课程,这样可以更轻松地从另一个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);
        }
    });
}