C# 在 Unity 中使用具有可等待异步方法的库,无需锁定主线程

问题描述 投票:0回答:1

我有一个这样的脚本:

public class Example : Monobehaviour
{
    public async void Start()
    {
        LibraryClass service = new LibraryClass();

        bool result = await service.Login();
        if (result == false)
        {
            Debug.LogError("Login Failed !!");
            return;
        }

        result = await service.Load();
        if (result == false)
        {
            Debug.LogError("Load Failed !!");
            return;
        }
    }
}

LibraryClass 方法的签名类似于:

public async Task<bool> Login();

public async Task<bool> Load();

运行脚本时,Unity主线程被阻塞,需要等待很长时间才能完成所有操作。 我决定添加一个进度条,但是,当然,直到这两个方法执行结束时才会更新任何内容。 我决定将脚本更改为如下所示:

public class Example : Monobehaviour
{
    private IEnumerator Start()
    {
        LibraryClass service = new LibraryClass();
        WaitForEndOfFrame wait = new WaitForEndOfFrame();

        Task<bool> task = service.Login();
        while (task.IsCompleted == false)
        {
            yield return wait;
        }
    
        if (task.Result == false)
        {
            Debug.LogError("Login Failed !!");
            return;
        }

        task = service.Load();
        while (task.IsCompleted == false)
        {
            yield return wait;
        }
    
        if (task.Result == false)
        {
            Debug.LogError("Load Failed !!");
            return;
        }
    }
}

我注意到了一个改进,但主线程仍然会被阻塞并停止 UI 和游戏其余部分的更新,因为我认为该库内部在不同的操作上使用了几种不同的等待(HTTP 请求、加载 zip 文件内容)等...)我无法控制,因为是由我正在使用的这 2 个公共方法触发的。

有没有办法避免Unity主线程完全阻塞?

c# unity-game-engine task coroutine
1个回答
0
投票

好吧,大家,这是我的解决方案:我将任务封装在线程中,我运行线程并等待线程。 该解决方案可带来丝般流畅的性能,甚至不会出现卡顿现象。 这是更改后的示例代码:

public class Example : Monobehaviour
{
    private bool _threadRunning = false;

    private IEnumerator Start()
    {
        LibraryClass service = new LibraryClass();
        WaitForEndOfFrame wait = new WaitForEndOfFrame();

        Thread thread = new Thread(() => { LoginThreadWorker(service); });
        thread.Start();
        _threadRunning = true;
    
        while (_threadRunning)
        {
            yield return wait;
        }

        thread = new Thread(() => { LoginThreadWorker(service); });
        thread.Start();
        _threadRunning = true;
    
        while (_threadRunning)
        {
            yield return wait;
        }
    }

    private void LoginThreadWorker(LibraryClass service)
    {
        Task<bool> task = service.Login();
        task.Wait();
    
        if (task.Result == false)
        {
            Debug.LogError("Login Failed !!");
        }
        else
        {
            // TODO - successfully logged in.
        }
    
        _threadRunning = false;
    }

    private void LoadThreadWorker(LibraryClass service)
    {
        Task<bool> task = service.Load();
        task.Wait();
    
        if (task.Result == false)
        {
            Debug.LogError("Load Failed !!");
        }
        else
        {
            // TODO - successfully loaded.
        }
    
        _threadRunning = false;
    }
}

因此,基本上,阻塞 Unity 主线程的 System.Threading.Task 现在被封装在封装在协程中的 System.Threading.Thread 中。 也许这不是最优雅的解决方案,但它对我有用,所以,我决定也在这里分享。

请注意:有效,因为我从库中获得的System.Threading.Task方法需要直接更改任何UnityEngine对象。如果您的任务正在与引擎中的任何内容进行交互,那么这种操作不适合您,因为您无法从 Unity 主线程外部更改 UnityEngine 对象。

© www.soinside.com 2019 - 2024. All rights reserved.