【问题标题】:How to yield coroutine with custom IEnumerator class [closed]如何使用自定义 IEnumerator 类产生协程 [关闭]
【发布时间】:2020-12-29 02:31:32
【问题描述】:

我找到了asynchronously loading models in Unity 的代码。我想知道它是如何工作的,这样我就可以做一些类似的事情来运行异步函数并在协程中从中检索一个值。我在网上找不到任何使用相同结构的东西,但我可能不知道要搜索什么,我认为这个示例没有使用异步流。

我感兴趣的代码是:

    IEnumerator Start()
    {
        AssetBundleCreateRequest bundleLoadRequest = AssetBundle.LoadFromFileAsync(pathToBundle);
        yield return bundleLoadRequest;

        AssetBundle myLoadedAssetBundle = bundleLoadRequest.assetBundle;
        
        ...
     }

我不确定“AssetBundleCreateRequest”是什么类型的对象,它可以使用异步函数进行初始化,在协程中产生,然后在协程继续时能够提取属性。我想在协程中运行我自己的异步函数,并在它完成后从中提取返回值。

【问题讨论】:

    标签: c# unity3d asynchronous coroutine


    【解决方案1】:

    Unity 在内部只是简单地创建一个 IEnumeratoryields 每帧直到异步加载任务完成。

    你可以例如做类似的事情

    public static IEnumerator WaitUntilIsDoneAsync(Action action)
    {
        var task = task.Run(() => action?.Invoke());
    
        while (!task.IsCompleted)
        {
            yield return null;
        }
    }
    

    然后你可以等待一些长时间的异步工作完成,例如

    yield return WaitUntilIsDoneAsync(() =>
    {
        Thread.Sleep(3000);
    }
    

    当然,为了使它更有用,您可能更愿意像他们那样实现一个类,您还可以在其中存储任务结果以供以后访问等。

    这当然很大程度上取决于您期望返回的类型。

    它可以例如看起来像这样。请注意,我不是异步专家,并且从空中抓住这个例子..不知道它是否完全像那样工作,但只是解释在后台或多或少发生了什么。

    所以为了yield你只需要anyIEnumerator,它不必通过StartCoroutine启动。

    所以你可以通过实现IEnumerator来实现这一点

    public class AsyncFileLoadRequest : IEnumerator
    {
        private readonly object _contentLock = new object();
        private Task<string> asyncLoadingTask;
    
        public string File content
        {
            get
            {
                lock(_contentLock)
                {
                    return _fileContent;
                }
            }
        }
        
        public AsyncFileLoadRequest(string path)
        {
            asyncLoadTask = File.ReadAllTextAsync(path);
            AwaitTaskAsync();
        }
    
        #region IEnumerator implementation
    
        object IEnumerator.Current => null;
    
        public void Reset() { }
    
        public bool MoveNext()
        {
            // As long as this returns true the coroutine will wait
            return !asyncLoadTask.IsCompleted;
        }
    
        #endregion IEnumerator implementation
    
        private async void AwaitTaskAsync()
        {
            lock(_contentLock)
            {
                _fileContent = await asyncLoadTask;
            }    
        }
    }
    

    所以你可以做类似的事情

    var request = new AsyncFileLoadRequest(filePath);
    
    yield return request;
    
    var content = request.FileContent;
    

    或者像他们一样使用工厂方法:

    public static class FileIOStuff
    {
        public static AsyncFileLoadRequest LoadFileAsync(string path)
        {
            return new AsyncFileLoadRequest (path);
        } 
    }
    

    和它一样的称呼

    var request = FileIOStuff.LoadFileAsync (filePath);
    
    yield return request;
    
    var content = request.FileContent;
    

    【讨论】:

    • 这是有道理的,但我仍然缺少的部分是您将如何实现该类,以便您可以只产生类变量。我也不确定使用 async 函数设置类是如何工作的,因为它不像是在构造函数中初始化代码。
    • @AlexanderFryer 是的,它正在调用构造函数。静态方法AssetBundle.LoadFromFileAsync 创建并重新运行new AssetBundleCreateRequest(...)
    • 太棒了!这看起来像我想要的。我会试一试你的代码,看看我能不能得到一些工作!
    • 我发现我需要实现 IEnumerator 而不是 IEnumerable 并用我的解决方案更新了我的问题。感谢您的帮助!
    • @AlexF 感谢反馈我更新了答案以改用IEnumerator
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-12
    • 1970-01-01
    • 2021-03-16
    • 2021-11-01
    • 1970-01-01
    相关资源
    最近更新 更多