一、啥是WPF MVVM和异步命令

咱先说说WPF MVVM。WPF就是Windows Presentation Foundation,它是微软搞出来的用来做Windows桌面应用程序界面的技术。而MVVM呢,是一种设计模式,它把界面和业务逻辑分开了。打个比方,界面就像是演员,业务逻辑就像是剧本,MVVM让它们各干各的事儿,互不干扰。

异步命令呢,就是在程序里执行一些比较耗时的任务,比如从网上下载东西、读取大文件啥的。如果不用异步命令,程序就会卡在那儿,界面也动不了,用户体验就特别差。用了异步命令,程序可以一边执行耗时任务,一边让界面正常响应用户操作。

二、为啥要在WPF MVVM里用异步命令

应用场景

想象一下,你做了个桌面程序,要从数据库里查大量的数据。要是用同步方式,程序就会一直等着数据查完,这期间界面就跟死机了似的。但要是用异步命令,程序可以在后台查数据,界面还能正常操作,用户可以干别的事儿,等数据查完了再通知用户。

技术优缺点

优点:

  • 提升用户体验:界面不会卡顿,用户可以继续操作。
  • 提高程序性能:可以同时处理多个任务,充分利用计算机资源。

缺点:

  • 代码复杂度增加:需要处理异步操作的各种情况,比如错误处理、取消任务等。
  • 调试困难:异步操作的执行顺序不太好把握,调试起来比较麻烦。

三、实现异步命令的步骤

1. 创建异步命令类

我们先创建一个异步命令类,用来处理异步任务。以下是C#代码示例:

// 技术栈:C#
using System;
using System.Threading.Tasks;
using System.Windows.Input;

// 异步命令类
public class AsyncRelayCommand : ICommand
{
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;

    // 构造函数,接收执行方法和判断是否可以执行的方法
    public AsyncRelayCommand(Func<Task> execute, Func<bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }

    // 判断是否可以执行命令
    public bool CanExecute(object parameter)
    {
        return _canExecute == null || _canExecute();
    }

    // 命令执行时触发的事件
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    // 执行命令
    public async void Execute(object parameter)
    {
        await _execute();
    }
}

这个类实现了ICommand接口,有三个主要方法:CanExecute用来判断命令是否可以执行,CanExecuteChanged是一个事件,当命令的可执行状态改变时会触发,Execute是执行命令的方法。

2. 在ViewModel里使用异步命令

接下来,我们在ViewModel里使用这个异步命令。示例代码如下:

// 技术栈:C#
using System;
using System.Threading.Tasks;
using System.Windows.Input;

// ViewModel类
public class MainViewModel
{
    // 异步命令属性
    public ICommand AsyncCommand { get; private set; }

    // 构造函数
    public MainViewModel()
    {
        // 创建异步命令
        AsyncCommand = new AsyncRelayCommand(DoAsyncTask);
    }

    // 异步任务方法
    private async Task DoAsyncTask()
    {
        try
        {
            // 模拟一个耗时任务
            await Task.Delay(2000);
            // 任务完成后可以在这里更新界面数据
            Console.WriteLine("异步任务完成");
        }
        catch (Exception ex)
        {
            // 处理异常
            Console.WriteLine($"异步任务出错: {ex.Message}");
        }
    }
}

在这个ViewModel里,我们创建了一个AsyncCommand属性,它是一个异步命令。在构造函数里,我们把DoAsyncTask方法传给了AsyncRelayCommand类的构造函数。DoAsyncTask方法是一个异步方法,里面用Task.Delay模拟了一个耗时任务。

3. 在View里绑定命令

最后,我们在View里绑定这个命令。以下是XAML代码示例:

<!-- 技术栈:XAML -->
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <!-- 按钮绑定异步命令 -->
        <Button Content="执行异步任务" Command="{Binding AsyncCommand}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
    </Grid>
</Window>

在这个XAML里,我们创建了一个按钮,把它的Command属性绑定到了ViewModel里的AsyncCommand。这样,当用户点击按钮时,就会执行异步任务。

四、处理后台任务的执行与状态反馈

显示任务状态

我们可以在界面上显示任务的状态,比如正在执行、已完成、出错等。以下是修改后的ViewModel代码:

// 技术栈:C#
using System;
using System.Threading.Tasks;
using System.Windows.Input;

// ViewModel类
public class MainViewModel
{
    // 异步命令属性
    public ICommand AsyncCommand { get; private set; }
    // 任务状态属性
    public string TaskStatus { get; set; }

    // 构造函数
    public MainViewModel()
    {
        // 创建异步命令
        AsyncCommand = new AsyncRelayCommand(DoAsyncTask);
        TaskStatus = "准备执行";
    }

    // 异步任务方法
    private async Task DoAsyncTask()
    {
        try
        {
            TaskStatus = "正在执行";
            // 模拟一个耗时任务
            await Task.Delay(2000);
            TaskStatus = "已完成";
            Console.WriteLine("异步任务完成");
        }
        catch (Exception ex)
        {
            TaskStatus = $"出错: {ex.Message}";
            Console.WriteLine($"异步任务出错: {ex.Message}");
        }
    }
}

在这个ViewModel里,我们添加了一个TaskStatus属性,用来表示任务的状态。在DoAsyncTask方法里,我们根据任务的执行情况更新这个属性。

然后,我们在XAML里绑定这个属性:

<!-- 技术栈:XAML -->
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <!-- 按钮绑定异步命令 -->
        <Button Content="执行异步任务" Command="{Binding AsyncCommand}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        <!-- 显示任务状态 -->
        <TextBlock Text="{Binding TaskStatus}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20"/>
    </Grid>
</Window>

这样,用户就可以在界面上看到任务的状态了。

取消任务

有时候,用户可能想取消正在执行的任务。我们可以通过CancellationToken来实现。以下是修改后的ViewModel代码:

// 技术栈:C#
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;

// ViewModel类
public class MainViewModel
{
    // 异步命令属性
    public ICommand AsyncCommand { get; private set; }
    public ICommand CancelCommand { get; private set; }
    // 任务状态属性
    public string TaskStatus { get; set; }
    private CancellationTokenSource _cancellationTokenSource;

    // 构造函数
    public MainViewModel()
    {
        // 创建异步命令
        AsyncCommand = new AsyncRelayCommand(DoAsyncTask);
        CancelCommand = new AsyncRelayCommand(CancelTask);
        TaskStatus = "准备执行";
    }

    // 异步任务方法
    private async Task DoAsyncTask()
    {
        _cancellationTokenSource = new CancellationTokenSource();
        try
        {
            TaskStatus = "正在执行";
            // 模拟一个耗时任务
            await Task.Delay(5000, _cancellationTokenSource.Token);
            TaskStatus = "已完成";
            Console.WriteLine("异步任务完成");
        }
        catch (TaskCanceledException)
        {
            TaskStatus = "任务已取消";
            Console.WriteLine("异步任务已取消");
        }
        catch (Exception ex)
        {
            TaskStatus = $"出错: {ex.Message}";
            Console.WriteLine($"异步任务出错: {ex.Message}");
        }
    }

    // 取消任务方法
    private async Task CancelTask()
    {
        if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
        {
            _cancellationTokenSource.Cancel();
        }
    }
}

在这个ViewModel里,我们添加了一个CancelCommand,用来取消任务。在DoAsyncTask方法里,我们创建了一个CancellationTokenSource,并把它的Token传给了Task.Delay方法。当用户点击取消按钮时,会调用CancelTask方法,取消任务。

然后,我们在XAML里添加取消按钮:

<!-- 技术栈:XAML -->
<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <!-- 按钮绑定异步命令 -->
        <Button Content="执行异步任务" Command="{Binding AsyncCommand}" HorizontalAlignment="Left" VerticalAlignment="Center" Margin="100,0,0,0"/>
        <!-- 取消按钮 -->
        <Button Content="取消任务" Command="{Binding CancelCommand}" HorizontalAlignment="Right" VerticalAlignment="Center" Margin="0,0,100,0"/>
        <!-- 显示任务状态 -->
        <TextBlock Text="{Binding TaskStatus}" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="0,0,0,20"/>
    </Grid>
</Window>

五、注意事项

  • 异常处理:在异步任务里,一定要处理好异常,不然程序可能会崩溃。
  • 线程安全:在更新界面数据时,要注意线程安全,避免出现界面更新混乱的问题。
  • 资源管理:使用CancellationToken时,要及时释放资源,避免内存泄漏。

六、文章总结

通过以上步骤,我们就可以在WPF MVVM里实现异步命令,处理后台任务的执行与状态反馈了。异步命令可以提升用户体验,让程序更加流畅。同时,我们还可以通过显示任务状态和取消任务等功能,让用户更好地控制程序。不过,实现异步命令也会增加代码的复杂度,需要我们仔细处理各种情况。