一、啥是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里实现异步命令,处理后台任务的执行与状态反馈了。异步命令可以提升用户体验,让程序更加流畅。同时,我们还可以通过显示任务状态和取消任务等功能,让用户更好地控制程序。不过,实现异步命令也会增加代码的复杂度,需要我们仔细处理各种情况。
评论