↓ 跳过正文

C#(WPF)-回调

作者
Hexin
在确定的逻辑与不确定的现实之间,做一名耐心的构建者。

一、什么是回调(Callback)
#

回调(Callback) 指的是:

把一个方法作为参数传递给另一个方法,在特定时机由被调用方反向调用该方法。

换句话说:

调用者 → 把函数传给被调用者 → 被调用者在合适时机执行这个函数

核心思想:

A 调用 B
B 在未来某个时间点 再调用 A 提供的方法

简单流程:

Main
 └── 调用 Process()
        └── 执行逻辑
        └── 调用 Callback()

二、回调的主要作用
#

作用 说明
异步通知 异步操作完成后通知调用者
事件驱动 响应事件(按钮点击、状态变化)
解耦合 调用者与执行逻辑分离
扩展性 不修改原代码即可扩展
自定义行为 把行为作为参数传递
管道处理 多个步骤连续处理数据

三、C#回调的实现方式
#

C#提供多种实现方式,本质都是函数引用。

主要方式:

委托 Delegate
Action / Func
事件 Event
接口回调
传统异步 AsyncCallback
Task + async/await
回调链

四、委托(Delagate)回调
#

委托是C#回调的基础机制

1.定义委托
#

public delegate void ProcessCallback(string result);

含义:

一个指向方法的类型
要求方法签名:
void Method(string)

2.使用委托回调
#

public class Processor
{
    public void ProcessDate(string data, ProcessCallback callback)
    {
        Console.WriteLine($"处理数据:{data}");

        string result = $"已处理:{data}";

        callback?.Invoke(result);
    }
}

调用:

class Program
{
    static void Main()
    {
        var processor = new Processor();

        processor.ProcessData("测试数据", OnProcessComplete);
    }

    static void OnProcessComplete(string result)
    {
        Console.WriteLine($"回调结果");
    }
}

执行流程:

Main
 ↓
ProcessData()
 ↓
callback.Invoke()
 ↓
OnProcessComplete()

五、Action / Func 泛型委托
#

.NET内置委托

优点:

  • 不需要自定义 delegate
  • 更简洁

1.Action(无返回值)
#

public void Process(string data, Action<string> callback)
{
    string result = $"$处理问题:{data}";
    callback?.Invoke(result);
}

调用:

processor.Process("数据", result =>
{
    Console.WriteLine(result);
});

2.Func(有返回值)
#

public int Transform(int x, Func<int, int> transform)
{
    return transform(x);
}

调用:

int result = Transform(5, x => x * 2);

3.Action / Func 规则
#

Action

Action
Action<T>
Action<T1, T2>

返回值:

void

Func

Func<T>
Func<T, TResult>
Func<T1, T2, TResult>

最后一个是返回值

例:

Func<int, int>
输入 int
返回 int

六、事件(Event) 回调
#

事件是基于委托的发布-订阅模式

常用于:

UI
状态通知
系统事件

定义事件
#

public class TemperatureMonitor
{
    public event Action<double> OnTemperatureChanged;

    private double _temperature;

    public double Temperature
    {
        get => _temperature;
        set
        {
            if (Math.Abs(_temperature - value) > 0.1)
            {
                _temperature = value;
                OnTemperatureChanged?.Invoke(value);
            }
        }
    }
}

订阅事件
#

monitor.OnTemperatureChanged += HandleTemperature;

或

monitor.OnTemperatureChanged += temp =>
{
    Console.WirteLine($"温度变化{temp}");
};

触发事件
#

monitor.Temperature = 25.5

执行:

OnTemperatureChanged
 ├── HandleTemperature
 └── Lambda

七、接口回调(Interface Callback)
#

适用于:

  • 回调方法很多

  • 需要回调协议

  • 需要扩展

定义接口
#

public interface IProcessCallback
{
    void OnComplete(string result);
    void OnError(Exception ex);
}

实现接口
#

public class LoggerCallback : IProcessCallback
{
    public void OnComplete(string result)
    {
        Console.WriteLine($"完成:{result}");
    }

    public void OnError(Exception ex)
    {
        Console.WriteLine($"错误:{ex.Message}");
    }
}

使用
#

public void Process(string data, IProcessCallback callback)
{
    try
    {
        callback.OnComplete("成功");
    }
    catch (Exception ex)
    {
        callback.OnError(ex);
    }
}

优点
#

结构清晰
支持多个回调方法
可扩展

八、传统异步回调(AsyncCallback)
#

旧版 .NET异步模式:

Asynchronous Programming Model

核心:

BeginXXX
EndXXX
AsyncCallback

示例:

processor.BeginProcess(data, callback, state);

回调:

void Callback(IAsyncResult ar)
{
    processor.EndProcess(ar);
}

但现在基本 已被 Task 替代。

九、Task + async/await(现代异步)
#

异步方法
#

public async Task<string> ProcessAsync(string data)
{
    await Task.Delay(1000);
    return $"处理完成{data}";
}

ContinueWith回调
#

processor.ProcessAsync("数据")
    	.ContinueWith(t =>
        {
            Console.WriteLine(t.Result);
        });

async回调
#

await processor.ProcessWithCallbackAsync("数据", async result =>
{
    Console.WriteLine(result);
});

十、回调链(Callback Chain)
#

类似管道处理Pipeline。

public void ProcessChain(string data, params Action<string>[] callbacks)
{
    string current = data;

    foreach (var cb in callbacks)
    {
        cb(current);
        current = $"处理:{current}";
    }
}

调用:

chain.ProcessChain("原始",
    x => Console.WriteLine($"步骤1 {x}"),
    x => Console.WriteLine($"步骤2 {x}"),
    x => Console.WriteLine($"步骤3 {x}")
);

十一、实际开发选择
#

场景 推荐
简单回调 Action / Func
事件通知 event
复杂回调协议 interface
异步操作 async / await
链式处理 回调链
旧代码维护 AsynCallback

Demo:模拟一个大文件分析工具
#

功能:

  • 用户点击 开始分析
  • 后台线程 异步处理数据
  • 进度回调
  • 日志回调
  • 完成回调
  • 错误回调
  • UI自动更新(MVVM + INotifyPropertyChanged)
  • 取消操作
  • 事件回调 + 委托回调 + async/await 混合

项目结构:

WpfCallbackDemo
│
├── MainWindow.xaml
├── MainWindow.xaml.cs
│
├── ViewModels
│     └── MainViewModel.cs
│
├── Services
│     └── DataAnalyzer.cs
│
├── Callbacks
│     └── IAnalyzerCallback.cs
│
├── Utils
│     └── RelayCommand.cs

架构关系:

UI (WPF)
   │

ViewModel
   │

Service (DataAnalyzer)
   │
   ├── 事件回调 Event
   ├── 委托回调 Action
   ├── 接口回调 Interface
   └── async/await
<Window
    x:Class="WpfCallbackDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:WpfCallbackDemo"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:vm="clr-namespace:WpfCallbackDemo.ViewModels"
    Title="Callback Analyzer"
    Width="500"
    Height="350"
    mc:Ignorable="d">

    <Window.DataContext>
        <vm:MainViewModel />
    </Window.DataContext>

    <Grid Margin="20">
        <StackPanel>

            <Button
                Height="40"
                Command="{Binding StartCommand}"
                Content="Start Analysis" />

            <Button
                Height="40"
                Margin="0,10,0,0"
                Command="{Binding CancelCommand}"
                Content="Cancel" />

            <ProgressBar
                Height="25"
                Margin="0,10,0,0"
                Maximum="100"
                Value="{Binding Progress}" />

            <ListBox
                Height="150"
                Margin="0,10,0,0"
                ItemsSource="{Binding Logs}" />

        </StackPanel>
    </Grid>
</Window>
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using WpfCallbackDemo.Callbacks;
using WpfCallbackDemo.Services;
using WpfCallbackDemo.Utils;

namespace WpfCallbackDemo.ViewModels
{
    public class MainViewModel : INotifyPropertyChanged, IAnalyzerCallback
    {
        private readonly DataAnalyzer analyzer = new DataAnalyzer();
        private CancellationTokenSource cts;

        public ObservableCollection<string> Logs { get; } = new();

        private int progress;
        public int Progress
        {
            get => progress;
            set
            {
                if (progress != value)
                {
                    progress = value;
                    OnPropertyChanged();
                }
            }
        }

        private string status;
        public string Status
        {
            get => status;
            set
            {
                if (status != value)
                {
                    status = value;
                    OnPropertyChanged();
                }
            }
        }

        public RelayCommand StartCommand { get; }
        public RelayCommand CancelCommand { get; }

        public MainViewModel()
        {
            StartCommand = new RelayCommand(async _ => await Start());
            CancelCommand = new RelayCommand(_ => Cancel());

            analyzer.ProgressChanged += OnProgressChanged;
        }

        private async Task Start()
        {
            Logs.Clear();
            cts = new CancellationTokenSource();

            await analyzer.AnalyzeAsync(
                "datafile.txt",
                log => Logs.Add(log), // 委托回调
                this, // 接口回调
                cts.Token);
        }

        private void Cancel()
        {
            cts?.Cancel();
        }

        // Event 回调
        private void OnProgressChanged(int value)
        {
            Progress = value;
            Logs.Add($"Progress: {value}%");
        }

        // 接口回调
        public void OnCompleted(string result)
        {
            Status = result;
            Logs.Add("Analysis completed successfully.");
        }

        public void OnError(string error)
        {
            Status = error;
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
using WpfCallbackDemo.Callbacks;

namespace WpfCallbackDemo.Services
{
    public class DataAnalyzer
    {
        // Event 回调
        public event Action<int> ProgressChanged;

        public async Task AnalyzeAsync(
            string file,
            Action<string> logCallback,
            IAnalyzerCallback callback,
            CancellationToken token)
        {
            try
            {
                for (int i = 0; i <= 10; i++)
                {
                    token.ThrowIfCancellationRequested();

                    await Task.Delay(500);

                    ProgressChanged?.Invoke(i * 10);

                    logCallback?.Invoke($"Processing block {i}");
                }

                string result = $"Analysis Completed: {file}";

                callback.OnCompleted(result);
            }
            catch (OperationCanceledException)
            {
                callback.OnError("Operation cancelled");
            }
            catch (Exception ex)
            {

                callback.OnError(ex.Message);
            }
        }
    }
}
namespace WpfCallbackDemo.Callbacks
{
    public interface IAnalyzerCallback
    {
        void OnCompleted (string result);
        void OnError (string ex);
    }
}

执行流程:

Button
 ↓
Command
 ↓
ViewModel.Start()
 ↓
DataAnalyzer.AnalyzeAsync()

产生三种回调

1. 事件回调 Event

Service
 ↓
ProgressChanged Event
 ↓
ViewModel.OnProgressChanged
 ↓
UI ProgressBar 更新

2. 委托回调 Action

Service
 ↓
logCallback.Invoke()
 ↓
ViewModel Logs.Add()
 ↓
UI ListBox 更新

3. 接口回调 Interface

Service
 ↓
callback.OnCompleted()
 ↓
ViewModel.OnCompleted()
 ↓
UI Status 更新

为什么WPF非常依赖回调?
#

WPF本质是 事件驱动 + 异步架构

UI不能阻塞:必须保持流程

所以必须:后台线程处理,回调通知UI.