通过委托执行多个函数


一、通过委托执行多个函数

假设我们在某个场景下需要运行一个逻辑:
关闭破真空-关闭吸真空-打开吸真空-延时200ms-处理其他逻辑-关闭吸真空-打开破真空-延时200ms-关闭破真空
其中每一个的执行都有可能是失败的(比如真空表值异常、通讯失败等原因),失败后就要报警等待人工检查处理而不应该继续往下执行

【以下代码比较长,但都是最简单的模型示例而已,请耐心看完】

我们常规的做法一般是这样:

public bool Run()
{
    bool flag = false;
    flag = CloseBreakVacuum();
    if (!flag)
    {
        return false;
    }
    flag = CloseSuctionVacuum();
    if (!flag)
    {
        return false;
    }
    flag = OpenSuctionVacuum();
    if (!flag)
    {
        return false;
    }
    Delay(200);
    OtherLogic();
    flag = CloseSuctionVacuum();
    if (!flag)
    {
        return false;
    }
    flag = OpenBreakVacuum();
    if (!flag)
    {
        return false;
    }
    Delay(200);
    flag = CloseBreakVacuum();
    if (!flag)
    {
        return false;
    }
    return flag;
}

public bool CloseBreakVacuum()
{
    // TODO..关闭破真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool CloseSuctionVacuum()
{
    // TODO..关闭吸真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool OpenBreakVacuum()
{
    // TODO..打开破真空,释放产品时防止产品黏在吸嘴上,读真空表气压值判断是否成功释放到产品
    return true;
}
public bool OpenSuctionVacuum()
{
    // TODO..打开吸真空,吸取产品,读真空表气压值判断是否成功吸取到产品
    return true;
}
public void Delay(int ms)
{
    // TODO..延时指定时间
}
public void OtherLogic()
{
    // TODO..处理其他逻辑
}

可以看出来这样的可读性太差了,那么如何优化呢?我们可以使用委托的做法:

public bool Run()
{
    bool flag = false;
    foreach (var func in new Func<bool>[] { CloseBreakVacuum, CloseSuctionVacuum, OpenSuctionVacuum })
    {
        flag = func();
        if(!flag)
        {
            return false;
        }
    }

    Delay(200);
    OtherLogic();

    foreach (var func in new Func<bool>[] { CloseSuctionVacuum, OpenBreakVacuum })
    {
        flag = func();
        if (!flag)
        {
            return false;
        }
    }

    Delay(200);

    foreach (var func in new Func<bool>[] { CloseBreakVacuum })
    {
        flag = func();
        if (!flag)
        {
            return false;
        }
    }

    return true;
}

public bool CloseBreakVacuum()
{
    // TODO..关闭破真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool CloseSuctionVacuum()
{
    // TODO..关闭吸真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool OpenBreakVacuum()
{
    // TODO..打开破真空,释放产品时防止产品黏在吸嘴上,读真空表气压值判断是否成功释放到产品
    return true;
}
public bool OpenSuctionVacuum()
{
    // TODO..打开吸真空,吸取产品,读真空表气压值判断是否成功吸取到产品
    return true;
}
public void Delay(int ms)
{
    // TODO..延时指定时间
}
public bool OtherLogic()
{
    // TODO..处理其他逻辑
    return true;
}

这样看,很明显,结构化的代码可读性是不是更加强了

但好处还不止这个,我们再发散下思维,每一步都可能错误,假设是通讯丢包出错,那我们再执行一次是不是就可能成功了?好了,现在欠收拾的产品经理新增了需求,每一步如果重复失败三次(次数需要可以修改)才认为是失败。
那原来的代码可能是这样处理的:

int retryTimes = 3; //重试次数
int errorCount = 0; //失败次数

public bool Run()
{
    bool flag = false;
    errorCount = 0;
    while (errorCount < retryTimes && !flag)
    {
        flag = CloseBreakVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }
    errorCount = 0;
    while (errorCount < retryTimes && !flag)
    {
        flag = CloseSuctionVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }
    errorCount = 0;
    while (errorCount < retryTimes && !flag)
    {
        flag = OpenSuctionVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }

    Delay(200);
    OtherLogic();

    errorCount = 0;
    while (errorCount < retryTimes && !flag)
    {
        flag = CloseSuctionVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }
    while (errorCount < retryTimes && !flag)
    {
        flag = OpenBreakVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }
    
    Delay(200);

    while (errorCount < retryTimes && !flag)
    {
        flag = CloseBreakVacuum();
        errorCount++;
    }
    if (!flag)
    {
        return false;
    }

    return true;
}

public bool CloseBreakVacuum()
{
    // TODO..关闭破真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool CloseSuctionVacuum()
{
    // TODO..关闭吸真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool OpenBreakVacuum()
{
    // TODO..打开破真空,释放产品时防止产品黏在吸嘴上,读真空表气压值判断是否成功释放到产品
    return true;
}
public bool OpenSuctionVacuum()
{
    // TODO..打开吸真空,吸取产品,读真空表气压值判断是否成功吸取到产品
    return true;
}
public void Delay(int ms)
{
    // TODO..延时指定时间
}
public void OtherLogic()
{
    // TODO..处理其他逻辑
}

而使用委托后可以这样处理:

int retryTimes = 3; //重试次数

public bool Run()
{
    bool flag = false;
    foreach (var func in new Func<bool>[] { CloseBreakVacuum, CloseSuctionVacuum, OpenSuctionVacuum })
    {
        flag = RetryFunc(func, retryTimes);
        if(!flag)
        {
            return false;
        }
    }

    Delay(200);
    OtherLogic();

    foreach (var func in new Func<bool>[] { CloseSuctionVacuum, OpenBreakVacuum })
    {
        flag = RetryFunc(func, retryTimes);
        if (!flag)
        {
            return false;
        }
    }

    Delay(200);

    foreach (var func in new Func<bool>[] { CloseBreakVacuum })
    {
        flag = RetryFunc(func, retryTimes);
        if (!flag)
        {
            return false;
        }
    }

    return true;
}

protected bool RetryFunc(Func<bool> func, int retryTimes = 3)
{
    bool flag = false;
    int errorCount = 0;
    while (errorCount < retryTimes && !flag)
    {
        flag = func();
        errorCount++;
    }
    return flag;
}

public bool CloseBreakVacuum()
{
    // TODO..关闭破真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool CloseSuctionVacuum()
{
    // TODO..关闭吸真空,读真空表气压值判断是否关闭成功
    return true;
}
public bool OpenBreakVacuum()
{
    // TODO..打开破真空,释放产品时防止产品黏在吸嘴上,读真空表气压值判断是否成功释放到产品
    return true;
}
public bool OpenSuctionVacuum()
{
    // TODO..打开吸真空,吸取产品,读真空表气压值判断是否成功吸取到产品
    return true;
}
public void Delay(int ms)
{
    // TODO..延时指定时间
}
public bool OtherLogic()
{
    // TODO..处理其他逻辑
    return true;
}

好了,本文章就到这里了,具体区别,需要自己细细体悟,具体用法,需要举一反三

<think>嗯,用户遇到了C#委托链中的一个问题:当一个函数执行后,下一个函数没有被调用。我需要帮他解决这个问题。首先,我得回忆一下C#委托的基本概念和用法。委托类似于函数指针,可以链式组合多个方法,形成一个调用列表。当调用委托时,这些方法会按顺序执行。 用户的问题可能出在委托的调用方式上。比如,是否正确地使用+=来添加方法委托链?或者是没有正确处理返回值和异常?例如,如果委托有返回值,使用Delegate.Combine可能不会正确触发所有方法,因为多播委托的返回值通常是最后一个方法的返回值,而其他方法可能被忽略。 另外,可能涉及到线程问题。比如,如果某个方法内部有异步操作,但没有正确等待,可能导致后续方法没有按预期执行。或者,在异步环境下,委托链的调用没有正确使用async/await,导致后续方法没有被触发。 还有可能,委托链中的某个方法抛出了异常,导致整个调用链中断,后面的方法自然就不会执行了。这时候需要检查是否有异常处理机制,比如try-catch块来捕获异常,确保后续方法能继续执行。 再想想用户提供的引用内容,引用[1]提到订阅调用是非阻塞的,Create委托在线程池上执行,所有处理程序也在线程池上运行。这可能涉及到异步编程的问题。如果委托链中的某个方法是异步的,但没有正确等待,可能导致后续方法在未完成的情况下就被调用了,或者根本没有被调用。需要确保异步方法正确使用await,或者使用Task.ContinueWith来链式执行。 引用[3]提到托管代码应该通过托管包装与非托管代码交互,这可能和问题无关,但可以忽略。引用[4]提到执行状态包括CPU寄存器、程序计数器等,可能涉及到线程上下文的问题,但暂时不确定是否相关。 可能的解决方案步骤包括:检查委托的添加方式是否正确,使用+=操作符;处理返回值为void的委托;检查异常处理;确保异步方法正确同步;以及检查委托是否为null等。 另外,用户提供的代码示例中,如果使用Delegate.Combine手动组合委托,可能需要用GetInvocationList来逐个调用,或者直接使用+=和-=来管理调用列表。如果委托有返回值,可能需要遍历调用列表,显式调用每个方法。 总结,可能的错误点包括:未正确组合委托链、异步方法未正确处理、异常导致中断、委托为null等。需要逐一排查这些情况,并提供相应的解决方案和示例代码。</think>### 问题分析:C#委托链未按顺序调用函数的原因 当C#委托链中的函数未按预期调用下一个函数时,通常与以下原因相关: 1. **委托链未正确组合** - 必须使用`+=`运算符将多个方法添加到委托链,而不是直接赋值(`=`),否则会覆盖之前的订阅[^1]。 - 错误示例:`myDelegate = Method1; myDelegate = Method2;`(仅保留最后一个方法) 2. **返回值处理不当** - 若委托有非`void`返回值,多播委托默认仅返回最后一个方法执行结果,可能导致中间方法未正确触发。需遍历`GetInvocationList()`显式调用每个方法。 3. **异步未正确同步** - 若委托包含异步操作(如`async/await`),未正确等待会导致后续方法提前执行或未执行。需通过`Task.ContinueWith`或`await`链式调用。 4. **异常导致中断** - 委托链中某个方法抛出异常会中断后续调用,需在单个方法内添加异常处理逻辑。 --- ### 解决方案与示例代码 #### 1. 基础委托链配置(同步方法) ```csharp public delegate void MyDelegate(); MyDelegate myDelegate = null; myDelegate += Method1; // 正确使用 += 添加方法 myDelegate += Method2; myDelegate?.Invoke(); // 依次调用 Method1 和 Method2 void Method1() => Console.WriteLine("Method1"); void Method2() => Console.WriteLine("Method2"); ``` #### 2. 处理有返回值的委托 ```csharp public delegate int MyDelegate(); MyDelegate myDelegate = null; myDelegate += () => { Console.WriteLine("Method1"); return 1; }; myDelegate += () => { Console.WriteLine("Method2"); return 2; }; foreach (var handler in myDelegate.GetInvocationList()) { int result = ((MyDelegate)handler)(); // 显式调用每个方法 } ``` #### 3. 异步委托链同步 ```csharp public delegate Task MyAsyncDelegate(); MyAsyncDelegate asyncDelegate = null; asyncDelegate += async () => { await Task.Delay(1000); Console.WriteLine("Async Method1"); }; asyncDelegate += async () => { await Task.Delay(500); Console.WriteLine("Async Method2"); }; // 使用 Task.ContinueWith 确保顺序执行 asyncDelegate?.Invoke()?.ContinueWith(t => { if (t.IsFaulted) Console.WriteLine(t.Exception); }); ``` #### 4. 异常处理 ```csharp myDelegate += () => { try { // 可能抛出异常的代码 } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } }; ``` --- ### 关键验证点 - 检查委托组合方式是否使用`+=` - 异步方法是否通过`await`或`ContinueWith`同步 - 是否存在未捕获的异常 - 委托是否为`null`(使用`myDelegate?.Invoke()`避免空引用)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值