【Unity基础】异步调用与协程 Invoke && Coroutine (附加一些小例子)

本文详细介绍了Unity中Invoke和协程(Coroutine)的使用方法,包括Invoke函数的调用、InvokeRepeating的周期调用、IsInvoking的检查以及CancelInvoke的取消调用。同时,讲解了协程的启动、挂起和恢复,通过StartCoroutine执行无参数和带参数的协程,以及如何使用yield语句控制协程执行流程。此外,还提供了两个实例,展示了协程如何实现物体依次上升和计时器功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

异步调用:Invoke

Invoke 调用的函数不能带参数

Invoke(“printCount”, 2); 表示经过2秒钟之后调用printCount函数

InvokeRepeating(“printCount”, 3, 1); 经过3秒钟之后调用printCount函数,之后每隔1秒调用一次该函数

IsInvoking() 判断是否有通过Invoke方式调用的函数

CancelInvoke(); 取消这个脚本上所有的调用

CancelInvoke(string methodName); 取消名字为methodName的调用

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 一旦通过invoke启动了一个函数,该游戏对象是否激活失活无关,该组件是否有效无关
/// 该脚本一旦删除,其上所有invoke启动的函数都会取消执行
/// </summary>
public class Invoke : MonoBehaviour
{
    static int num;

    private void Awake()
    {
        print("Awake");
       
    }
    void Start()
    {
        //直接调用函数
        printCount();
        //Invoke调用的函数不能带参数
        Invoke("printCount", 2);//经过2秒钟之后调用printCount函数
        InvokeRepeating("printCount", 3, 1);//经过3秒钟之后调用printCount函数,之后每隔1秒调用一次该函数
        //Invoke("hasInvoke", 5);

    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// <summary>
    /// 注意:该函数不能有参数,如果有参数,异步调用执行不到该函数,不报错
    /// </summary>
    void hasInvoke()
    {
        //判断是否有通过Invoke方式调用的函数
        if (IsInvoking())
        {
            CancelInvoke();
        }
    }
    void printCount()
    {
        num++;
        print("num="+num);
    }

    
}

协同2.2开启协程(可以用方法也可以用字符串)

1
Coroutine StartCoroutine(IEnumerator routine)
开启一个协程,利用yield语句暂停在某点,yield返回值指定了什么时机协程恢复。对于多帧执行一次的行为非常适合用协程解决,它几乎没有什么性能开销。参数传递没有限制。

2
Coroutine StartCoroutine(string methodName, object value = null)
通过字符串开启一个协程,主要的目的是可以使用StopCoroutine来终止指定名字的函数,但是此种方法只能传递一个参数,性能上也有所损耗。
如下协程的启动方式
//形式一
StartCoroutine(CustomCorutineFn));
StartCoroutine(CustomCorutineFn(7));//向方法中传递参数
//形式二
StartCoroutine("CustomCorutineFn” );
StartCoroutine("CustomCorutineFn”,7);//向方法中传递参数

3
终止协议
1、void StopAllCoroutines()
终止运行在该MonoBehaviour上的所有协程。

2、void StopCoroutine(string methodName)
终止运行在该MonoBehaviour上的所有名字为methodName的协程。仅仅使用字符串开启的协程才可以使用该函数进行终止。

yield
yield是一种特殊类型的返回值语句,它可以确保函数在下一次被执行时,不是从头开始,而是从yield语句处开始,如果在函数中用了yield语句,需要把函数的返回值改成IEnumerator。yield后面可以有的表达式。

1、yield null(协程将在下一帧所有脚本的Update执行之后,再继续执行)
2、yield WaitForSeconds(协程在延迟指定时间,且当前帧所有脚本的Update全都执行结束后才继续执行)
3、WaitForEndOfFrame (一帧结束时,渲染之前)
4、yield WaitForFixedUpdate(协程在所有脚本的FixedUpdate执行之后,再继续执行,即等待物理更新时间0.02秒)
5.yield StartCoroutine(协程在指定协程执行结束后,再继续执行)6、yield wWW((协程在WWW下载资源完成后,再继续执行)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 如果协程启动之后,该脚本所在的游戏对象失活状态,该游戏对象上所有的协程停止,游戏对象激活也不会执行其上的协程
/// 跟该脚本组件是否有效无关
/// 该脚本一旦删除,其上所有StartCoroutine启动的协程都会取消执行
/// </summary>
public class Coroutine : MonoBehaviour
{
    public GameObject prefab;
    // Start is called before the first frame update
    void Start()
    {
        //普通函数调用方式
        //funCoroutine(3);
        //第一种:通过传入协程函数执行协程,这种方式不受参数的限制,不能制定该协程停止,只能通过StopAllCoroutines停止所有的协程
        //StartCoroutine(funCoroutine(3));


        //第二种:通过传入协程函数名字执行协程,注意,如果有参数而传递,该协程函数启动不了,而且报错,
        //这种启动方式可以通过StopCoroutine停止该协程函数
        //如果函数有参数,只能一个参数
        StartCoroutine("funCoroutine",3);

        StartCoroutine(createGameObject(1));
    }

    // Update is called once per frame
    void Update()
    {
        if(IsInvoking())
        {
            print("IsInvoking");
        }
    }

    /// <summary>
    /// 定义协程函数
    /// 返回值:IEnumerator
    /// 函数体:一定要有yield return语句
    /// </summary>
    /// <param name="time"></param>
    /// <returns></returns>
    IEnumerator funCoroutine(float waitTime)
    {
        print("start funCoroutine " + Time.time+" frameCount="+Time.frameCount);
        yield return null;//挂起协程,挂起一帧,等待时间到了之后继续执行下面的代码
        print("yield return null "+Time.time + " frameCount=" + Time.frameCount);
        yield return 100;//挂起一帧
        print("yield return 100 " + Time.time + " frameCount=" + Time.frameCount);

        yield return 100;//挂起一帧
        print("yield return 100 " + Time.time + " frameCount=" + Time.frameCount);

        yield return new WaitForSeconds(waitTime);//挂起指定时间,秒
        print("yield return new WaitForSeconds + waitTime " + +Time.time + " frameCount=" + Time.frameCount);

        yield return 1000000;//挂起一帧

        print("yield return 1000000 " + Time.time + " frameCount=" + Time.frameCount);
    }

    IEnumerator createGameObject(float dtTime)
    {
        while (true)
        {
            GameObject go= Instantiate<GameObject>(prefab);
            go.SetActive(true);
            go.transform.parent = transform;
            yield return new WaitForSeconds(dtTime);
            if(transform.childCount>=20)
            {
                StopAllCoroutines();
            }
        }
    }
}

例子:

1.在X正方向上有5个物体,依次排列开,实现从第一个物体上升到5米高时第二个物体开始上升,依次执行到最后一个物体。

思路
协程来控制是哪一个球向上移动,分别替换go游戏对象,等待五秒换下一个, 因为设置的速度是1m/s 所以正常来说5m左右就会停下,但也会有些许的误差,因为每帧的时间都不可能完全一样。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Spheres : MonoBehaviour
{
    GameObject go;
    int index;
    void Start()
    {
        StartCoroutine(goContol(5.0f));
    }
    void move()
    {
        if (go.transform.position.y >= 5.0f)
        {
            return;
        }
        go.transform.Translate(0, Time.deltaTime * 1, 0);
    }
    IEnumerator goContol(float rate)
    {
        while (index < transform.childCount)
        {
            go = transform.GetChild(index).gameObject;
            index++;
            yield return new WaitForSeconds(rate);
        }
    }
    void Update()
    {
        move();
    }
}

2.用协程做一个计时器
思路: 时分秒都是用秒来计算。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class Timer : MonoBehaviour
{
    float curSecond;
    Text timer;
    int seconds;
    int secondNum;
    int miniuteNum;
    int hourNum;

    public bool isPause = false;
    void Start()
    {
        
        timer = transform.GetComponent<Text>();
        StartCoroutine(Updata());
    }

    IEnumerator Updata()
    {
        while (true)
        {
            secondNum++;
            seconds++;
           
            if (secondNum == 60)
            {
                secondNum = 0;
            }
            
            yield return new WaitForSeconds(1.0f);

        }

    }
    void Update()
    {
        if (isPause == false)
        {
            string secstr;
            string minstr;
            string hourstr;
            miniuteNum = seconds % 3600 / 60;
            hourNum = seconds / 3600;
            if (secondNum<10)
            {
                 secstr = "0"+secondNum.ToString();
            }
            else
            {
                 secstr =  secondNum.ToString();
            }
            if (miniuteNum < 10)
            {
                minstr = "0" + miniuteNum.ToString();
            }
            else
            {
                minstr = miniuteNum.ToString();
            }
            if (hourNum < 10)
            {
                hourstr = "0" + hourNum.ToString();
            }
            else
            {
                hourstr = hourNum.ToString();
            }
            timer.text = hourstr + ":"+ minstr+":" +secstr;
        }
    }
}

03-21
<think>好的,用户现在问的是关于“vmmem”的问题。我需要先理解这个词的含义。根据我之前的知识库,“vmmem”通常虚拟机相关,特别是在Windows系统中。记得Windows的Hyper-V或者WSL2会使用这个进程。 首先,用户可能遇到了系统资源占用过高的问题,比如任务管理器里看到vmmem进程占用了大量内存或CPU。这时候他们想知道这是什么进程,是否安全,以及如何优化。 接下来,我需要确认vmmem的具体作用。它是虚拟机管理程序的一部分,负责分配管理虚拟机使用的物理资源。如果是WSL2用户,或者使用Hyper-V运行虚拟机,这个进程出现是正常的。 然后,用户可能的深层需求是解决资源占用问题。他们可能发现电脑变慢,或者风扇狂转,想找到解决办法。需要提供一些优化方法,比如调整内存分配、关闭不必要的虚拟机服务,或者检查是否有虚拟机没有正确释放资源。 另外,用户可能对突然出现的vmmem进程感到困惑,担心是恶意软件。需要解释它的合法性,并建议通过任务管理器查看其路径,确认是否位于系统目录,以排除病毒的可能性。 还要考虑不同的使用场景,比如开发人员使用WSL2进行编程,或者普通用户使用虚拟机软件。针对不同情况,给出相应的建议。例如,对于WSL2用户,可以指导如何修改.wslconfig文件来限制内存使用。 最后,确保回答结构清晰,分点说明问题原因解决方案,并给出预防措施,帮助用户全面理解解决问题。</think>以下是关于`vmmem`进程的详细解析: --- ### **一、进程定义** `vmmem`是Windows系统中**虚拟机内存管理**直接相关的核心进程,其名称全称为: $$Virtual\ Machine\ Memory\ Management\ Service$$ 主要负责: 1. 为Hyper-V虚拟机/WSL2子系统分配物理内存 2. 管理虚拟机宿主机之间的内存共享 3. 实现内存动态分配(Ballooning机制) --- ### **二、高资源占用原因** 当任务管理器显示`vmmem`占用过高(特别是内存)时,常见于: $$内存占用率 \propto \frac{运行的虚拟机数量 \times 分配内存}{宿主机物理内存}$$ 具体诱因: 1. **WSL2配置不当** - 默认无内存限制(可能占用50%物理内存) - 可通过创建`%UserProfile%\.wslconfig`设置: ```ini [wsl2] memory=4GB # 限制最大内存 processors=2 # CPU核心数 ``` 2. **Hyper-V虚拟机未释放资源** - 虚拟机暂停未关闭时仍占用内存 - 快照文件积累(.avhd文件膨胀) 3. **内存泄漏** - 旧版Windows 10(1903之前)存在已知内存泄漏BUG - 虚拟机驱动不兼容 --- ### **三、解决方案** #### **步骤1:基础排查** ```powershell # 查看WSL实例状态 wsl --list --verbose # 终止所有WSL实例 wsl --shutdown # 重启LxssManager服务 Get-Service LxssManager | Restart-Service ``` #### **步骤2:内存限制(WSL2专用)** 1. 创建配置文件: ```shell notepad %UserProfile%\.wslconfig ``` 2. 输入配置: ```ini [wsl2] memory=6GB # 根据物理内存调整(建议≤50%) swap=0 # 禁用交换空间 localhostForwarding=true ``` #### **步骤3:Hyper-V优化** ```powershell # 查看虚拟机状态 Get-VM | Format-Table Name, State # 完全关闭闲置虚拟机 Stop-VM -Name "VM_NAME" -Force # 调整动态内存配置 Set-VMMemory -VMName "VM_NAME" -DynamicMemoryEnabled $true ``` --- ### **四、进阶调试** 1. **内存转储分析** 使用`procdump -ma vmmem`生成dump文件,通过WinDbg分析: ```bash !address -summary # 查看内存分布 !vm # 显示虚拟内存信息 ``` 2. **性能计数器监控** 添加下列计数器: - `Hyper-V Dynamic Memory Baloon` - `Hyper-V Virtual Machine Bus\Write Bytes/sec` - `Memory\Available MBytes` --- ### **五、预防措施** 1. 定期清理虚拟机快照 2. 为WSL2设置资源上限 3. 升级到Windows 10 2004+或Windows 11(已修复内存泄漏) 4. 检查虚拟机集成服务版本: ```powershell Get-VMIntegrationService -VMName "VM_NAME" | Where-Object {$_.Name -eq "Guest Service Interface"} ``` --- **注意**:若无需使用虚拟机功能,可通过`控制面板 > 程序 > 启用或关闭Windows功能`中禁用**Hyper-V****Windows Hypervisor Platform**来彻底
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值