首页 > 解决方案 > 如何使用自定义 IEnumerator 类生成协程

问题描述

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

我感兴趣的代码是:

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

        AssetBundle myLoadedAssetBundle = bundleLoadRequest.assetBundle;
        
        ...
     }

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

标签: c#unity3dasynchronouscoroutine

解决方案


在内部,Unity 只是在异步加载任务完成之前每帧IEnumerator创建一个。yields

你可以例如做类似的事情

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你只需要any IEnumerator,它不必通过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;

推荐阅读