调用并行协程并等待它们全部结束

Call parallel coroutines and wait for all of them to be over

我有一些协程:

IEnumerator a(){ /* code */ }
IEnumerator b(){ /* code */ }
IEnumerator c(){ /* code */ }

我想创建一个并行调用 abc 的协程,但要等待它们全部完成才能继续,例如:

IEnumerator d(){
    StartCoroutine(a());
    StartCoroutine(b());
    StartCoroutine(c());
    wait until all of them are over
    print("all over");
}

显然我可以为每个协程使用布尔值来保存其当前状态,但由于这种方法不可扩展,我更喜欢更直接的解决方案。

当你安装这个package (source)时,它可以实现为异步协程混合方法:

using System.Collections;
using System.Threading.Tasks;
using UnityEngine;

public class TestCoroutines : MonoBehaviour
{

    void Start () => D();

    IEnumerator A () { yield return new WaitForSeconds(1f); print($"A completed in {Time.time}s"); }
    IEnumerator B () { yield return new WaitForSeconds(2f); print($"B completed in {Time.time}s"); }
    IEnumerator C () { yield return new WaitForSeconds(3f); print($"C completed in {Time.time}s"); }

    async void D ()
    {
        Task a = Task.Run( async ()=> await A() );
        Task b = Task.Run( async ()=> await B() );
        Task c = Task.Run( async ()=> await C() );

        await Task.WhenAll( a , b , c );

        print($"D completed in {Time.time}s");
    }

}

控制台输出:

A completed in 1.006965s
B completed in 2.024616s
C completed in 3.003201s
D completed in 3.003201s

您可以尝试 Invoke("MethodName",timeinFloat) 并在每个方法中添加一个计数器(int)/一个布尔值。全部完成后运行,根据counter/bool条件,就可以继续执行了。

如果Invoke时间设置为0,则在下一个更新帧周期运行

我用的方法,代码也比较清晰好用:

IEnumerator First() { yield return new WaitForSeconds(1f); }
IEnumerator Second() { yield return new WaitForSeconds(2f); }
IEnumerator Third() { yield return new WaitForSeconds(3f); }

IEnumerator d()
{
    Coroutine a = StartCoroutine(First());
    Coroutine b = StartCoroutine(Second());
    Coroutine c = StartCoroutine(Third());

    //wait until all of them are over
    yield return a;
    yield return b;
    yield return c;

    print("all over");
}

也可以使用协程背后的底层迭代器,自己调用MoveNext

在您的示例中,它将类似于

IEnumerator a(){ /* code */ }
IEnumerator b(){ /* code */ }
IEnumerator c(){ /* code */ }

IEnumerator d(){
    IEnumerator iea = a();
    IEnumerator ieb = b();
    IEnumerator iec = c();
    // Note the single | operator is intended here
    while (iea.MoveNext() | ieb.MoveNext() | iec.MoveNext()) {
        yield return null;
    }
    print("all over");
}

在此处查看有关 | 运算符的文档 https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/boolean-logical-operators#logical-or-operator-
它基本上是一个 || 运算符,但它将评估您的所有表达式,从而有效地推进每个迭代器,即使另一个迭代器已经完成。

Biswadeep Sarkar的回答很不错。我改进了一点。制作了一个用于等待并行协程的通用函数。欢迎使用和进一步修改。

  IEnumerator WaitForSomeCoroutines(params IEnumerator[] ienumerators)
    {
        Debug.Log($"Start time of parallel routines: {Time.time}");
        if (ienumerators != null & ienumerators.Length > 0)
        {
            Coroutine[] coroutines = new Coroutine[ienumerators.Length];
            for (int i = 0; i < ienumerators.Length; i++)
                coroutines[i] = StartCoroutine(ienumerators[i]);
            for (int i = 0; i < coroutines.Length; i++)
                yield return coroutines[i];
        }
        else 
            yield return null;
        Debug.Log($"End time of parallel routines: {Time.time}");
    }

所以,假设您有一些例程:

 IEnumerator A(float time) { yield return new WaitForSeconds(time); }
 IEnumerator B(float time) { yield return new WaitForSeconds(time); }
 IEnumerator C(float time) { yield return new WaitForSeconds(time); }    
  

您希望例程 D 等待例程 A、B 和 C 完成。您这样做:

  IEnumerator D()
        {
            // We wait until 3 other coroutines are finished.
           yield return StartCoroutine(WaitForSomeCoroutines(
                A(1f),
                B(3f),
                C(2f)));
    
           // Now we make our stuff.
           Debug.Log("Working on my stuff...");
        }

根据 Biswadeep Sarkar 的回答,这是另一个 feature/method 使用协程和 IEnumerators 的方法。

IEnumerator CountTill(int numberToCountTill) {
    for (int i = 0; i < numberToCountTill; i++) {
        yield return i;
    }
    yield return "Done";
}
IEnumerator a;
IEnumerator b;
IEnumerator c;

IEnumerator d()
{
    a = CountTill(1);
    b = CountTill(3);
    c = CountTill(9);

    StartCoroutine(a);
    StartCoroutine(b);
    StartCoroutine(c);

    //wait until all of them are over
    while (a.Current.ToString() != "Done" && 
           b.Current.ToString() != "Done" && 
           c.Current.ToString() != "Done")
    {
        yield return "Waiting";
    }


    print("all over");
}

这是您可以通过保持对函数的引用并定期检查值来检查协程 (IEnumerator) 状态的方法。潜在地,您还可以在执行其他方法的过程中开始做其他事情,例如更新进度条以显示您已达到的加载阶段,前提是您适当地更改了您的产量 return。如果您有两种方法试图轮流使用,请注意死锁。