C# 委托的三种调用方式:同步、异步与异步回调详解

闻溪
闻溪
发布时间:2026-10-14 | 5人浏览过
原创

在 C# 开发中,合理使用委托是提升应用响应速度和并行处理能力的关键。本文将深入解析委托的三种核心调用模式:同步调用、异步调用及异步回调。通过具体的代码实例对比 Invoke、BeginInvoke/EndInvoke 以及 AsyncCallback 的工作机制,帮助开发者理解线程阻塞差异,并掌握如何在耗时操作中保持主线程流畅。

委托定义与基础设置

在进行任何调用测试前,首先需要定义一个委托类型及其对应的处理方法。为了模拟耗时操作(如 I/O 密集型任务),我们在方法中加入了延迟模拟。

using System;
using System.Threading;

// 1. 定义委托:接受两个整数,返回一个整数
public delegate int AddHandler(int a, int b);

public class MathHelper
{
    public static int Add(int a, int b)
    {
        Console.WriteLine($"开始计算:{a} + {b}");
        Thread.Sleep(3000); // 模拟耗时3秒的操作
        Console.WriteLine("计算完成!");
        return a + b;
    }
}

第一步:同步调用(Sync Invoke)

这是最直观的调用方式。使用 Invoke 方法时,当前线程会被阻塞,直到委托指向的方法执行完毕并返回结果。适用于对实时性要求不高且流程简单的场景。

操作说明

  • 入口动作:创建委托实例并调用 Invoke。
  • 关键参数:Invoke(method, arg1, arg2),参数签名需与委托一致。
  • 完成状态:下一行代码仅在计算结束后执行。
public class SyncInvokeExample
{
    public static void Main()
    {
        Console.WriteLine("===== 同步调用 =====");
        AddHandler handler = new AddHandler(MathHelper.Add);
        
        // 同步调用会阻塞当前线程
        int result = handler.Invoke(1, 2);
        
        // 只有当 Add 方法执行完毕后,才会打印此句
        Console.WriteLine("继续做别的事情...");
        Console.WriteLine(result);
    }
}

第二步:异步调用(Async Invoke)

当需要执行繁重任务而不希望界面或主线程卡顿时,可使用 BeginInvoke 启动异步操作。它会将任务分配给线程池,立即返回 IAsyncResult 对象。主线程可继续执行后续逻辑,但在获取结果时必须调用 EndInvoke。

操作说明

  • 入口动作:调用 BeginInvoke 启动异步任务。
  • 关键参数:BeginInvoke(arg1, arg2, callback, state)。注意最后两个参数传 null 表示暂不处理回调。
  • 风险提示:BeginInvoke 和 EndInvoke 必须成对出现,否则可能导致内存泄漏。
public class AsyncInvokeExample
{
    public static void Main()
    {
        Console.WriteLine("===== 异步调用(无回调)=====");
        AddHandler handler = new AddHandler(MathHelper.Add);
        
        // 开始异步调用,立即返回 IAsyncResult
        IAsyncResult result = handler.BeginInvoke(1, 2, null, null);
        
        Console.WriteLine("主线程继续执行其他任务...");
        
        // 这里会阻塞主线程,直到异步操作完成
        // 如果前面已经做了大量工作,这里可以改为轮询 IsCompleted 属性
        Console.WriteLine(handler.EndInvoke(result));
    }
}
[]

第三步:异步回调(Async Callback)

为了解决 EndInvoke 被动阻塞的问题,可以在 BeginInvoke 中传入 AsyncCallback 委托。当后台任务完成时,系统会自动调用指定的回调函数,从而实现完全的非阻塞式处理。

操作说明

  • 入口动作:调用 BeginInvoke 并传入回调方法引用。
  • 回调逻辑:在回调函数内部,通过 AsyncDelegate 属性还原委托实例,调用 EndInvoke 获取结果。
  • 状态传递:利用 BeginInvoke 的第四个参数 state 向回调传递自定义数据。
public class AsyncCallbackExample
{
    public static void Main()
    {
        Console.WriteLine("===== 异步调用(带回调)=====");
        AddHandler handler = new AddHandler(MathHelper.Add);
        
        // 第三个参数指定回调函数,第四个参数传递状态信息
        IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(OnCalcComplete), "StateInfo: OK");
        
        Console.WriteLine("主线程释放,去处理其他业务逻辑...");
        
        // 防止主线程过快退出导致进程终止(实际应用中应合理控制生命周期)
        Thread.Sleep(4000);
    }

    /// 
    /// 异步完成后的回调函数
    /// 
    private static void OnCalcComplete(IAsyncResult result)
    {
        Console.WriteLine("回调函数被触发...");
        
        // 还原委托实例以调用 EndInvoke
        AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
        
        // 获取最终计算结果
        int total = handler.EndInvoke(result);
        
        // 获取 BeginInvoke 时传入的状态参数
        string state = result.AsyncState as string;
        
        Console.WriteLine($"结果: {total}, 状态信息: {state}");
    }
}

常见问题与注意事项

为什么 Invoke 的参数与委托一致?

Invoke 的设计目的是确保线程安全地执行方法。它会检查当前线程是否为 UI 线程(若从跨线程上下文调用),若是则直接执行,否则切换到 UI 线程执行。无论哪种情况,它都阻塞直到执行完毕,因此其签名必然匹配委托定义。

BeginInvoke 与 EndInvoke 的关系

BeginInvoke 负责将请求放入线程池并开始执行,而 EndInvoke 用于同步等待异步操作完成并提取返回值或异常。**切记**:即使不需要返回值,也必须调用 EndInvoke 来清理资源,否则会导致内存泄漏。

IAsyncResult.AsyncState 的作用

该属性允许开发者在发起异步调用时传递任意对象,并在回调函数中接收。这在需要区分不同异步任务或携带上下文信息时非常有用。

总结

C# 委托提供了灵活的执行控制手段。Invoke 简单直接但阻塞;BeginInvoke 结合 EndInvoke 实现了初步的异步化,但仍需主动干预获取结果;而结合 AsyncCallback 的方案则实现了最彻底的异步解耦。在实际开发中,对于复杂的异步场景,推荐进一步探索基于 Task 和 async/await 的现代异步编程模型,它们能更优雅地处理这些逻辑。

以上就是 C# 委托的三种调用方式(同步、异步、异步回调)的详细内容,更多关于 C# 并发编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表