赞
踩
WPF程序中,需要从几个网站下载内容。
private void NormalExecute() { Percentage = 0; var watch = Stopwatch.StartNew(); RunDownload(); watch.Stop(); var elapsedMS = watch.ElapsedMilliseconds; Message += $"Total execution time: {elapsedMS}"; } private List<string> PrepWebsites() { var output = new List<string>(); Message = string.Empty; output.Add("https://www.baidu.com"); output.Add("https://www.yahoo.net/"); output.Add("https://www.csdn.net/"); output.Add("https://www.douyin.com/"); output.Add("https://weixin.qq.com/"); Percentage = 0; Maximum = output.Count; return output; } private void RunDownload() { var websites = PrepWebsites(); foreach (var site in websites) { WebsiteModel result = DownloadWebsite(site); ReportWebsiteInfo(result); Percentage++; } } private WebsiteModel DownloadWebsite(string websiteURL) { WebsiteModel output = new WebsiteModel(); WebClient client = new WebClient(); output.WebsiteUrl = websiteURL; output.WebsiteData = client.DownloadString(websiteURL); return output; } private void ReportWebsiteInfo(WebsiteModel data) { Message += $"{data.WebsiteUrl} downloaded: {data.WebsiteData.Length} characters long.{Environment.NewLine}"; }
当点击“同步”按钮时,会发现WPF程序大概卡了3秒钟,最终将结果一次性呈现出来:
private async void AsyncExecute() { var watch = Stopwatch.StartNew(); // RunDownloadAsync(); 如果没有await,就会不等Download结束,就直接执行watch.Stop()了 await RunDownloadAsync(_cancellationTokenSource.Token); watch.Stop(); var elapsedMS = watch.ElapsedMilliseconds; Message += $"Total execution time: {elapsedMS}{Environment.NewLine}"; } /// <summary> /// 在async函数中,如果没有返回值,也不要写void,而是写Task /// </summary> /// <returns></returns> private async Task RunDownloadAsync() { var websites = PrepWebsites(); foreach (var site in websites) { WebsiteModel result = await Task.Run(() => DownloadWebsite(site)); ReportWebsiteInfo(result); Percentage++; } }
点击异步按钮后,WPF界面可以随意拖动,并且进度条、结果等都是一条一条输出到UI中的。
与第一种同步方式相比,
private async void ParalleAsyncExecute() { Percentage = 0; var watch = Stopwatch.StartNew(); await RunDownloadParallelAsync(); watch.Stop(); var elapsedMS = watch.ElapsedMilliseconds; Message += $"Total execution time: {elapsedMS}{Environment.NewLine}"; } private async Task RunDownloadParallelAsync() { var websites = PrepWebsites(); List<Task> tasks = new List<Task>(); foreach (var site in websites) { var task = DownloadWebsiteAsync(site).ContinueWith(t => { ReportWebsiteInfo(t.Result); Percentage++; }); tasks.Add(task); } await Task.WhenAll(tasks); } private async Task<WebsiteModel> DownloadWebsiteAsync(string websiteURL) { WebsiteModel output = new WebsiteModel(); WebClient client = new WebClient(); output.WebsiteUrl = websiteURL; output.WebsiteData = await client.DownloadStringTaskAsync(websiteURL); return output; } #endregion
第二种异步方式中,每创建一个Task,都会直接await
而在并行方式中,我们先只创建task,然后放到list中,最后await Task.WhenAll(tasks);
这样就可以让所有task以并行的方式同时运行,这样即不阻塞UI,又能更快的执行任务。
其实在第一种方式中,如果只是不想阻塞线程,直接写成下面即可,可以实现和第二种异步方式完全相同的效果
private async void NormalExecute()
{
Percentage = 0;
var watch = Stopwatch.StartNew();
await Task.Run(()=> RunDownload());
watch.Stop();
var elapsedMS = watch.ElapsedMilliseconds;
Message += $"Total execution time: {elapsedMS}";
}
如果用上Parallel,也可以非常简单的实现第三种并行效果
private async void NormalExecute() { Percentage = 0; var watch = Stopwatch.StartNew(); var websites = PrepWebsites(); await Task.Run(() => Parallel.ForEach(websites, site => { WebsiteModel result = DownloadWebsite(site); ReportWebsiteInfo(result); Percentage++; })); watch.Stop(); var elapsedMS = watch.ElapsedMilliseconds; Message += $"Total execution time: {elapsedMS}"; }
public class MainWindowViewModel : BindableBase { private CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource(); public MainWindowViewModel() { NormalCommand = new DelegateCommand(NormalExecute); AsyncCommand = new DelegateCommand(AsyncExecute); ParallelAsyncCommand = new DelegateCommand(ParalleAsyncExecute); CancelCommand = new DelegateCommand(() => _cancellationTokenSource.Cancel()); } private async void AsyncExecute() { var watch = Stopwatch.StartNew(); try { await RunDownloadAsync(_cancellationTokenSource.Token); } catch (OperationCanceledException) { Message += "Async download was canceled.\n"; } watch.Stop(); var elapsedMS = watch.ElapsedMilliseconds; Message += $"Total execution time: {elapsedMS}{Environment.NewLine}"; } private async Task RunDownloadAsync(CancellationToken cancellationToken) { var websites = PrepWebsites(); foreach (var site in websites) { WebsiteModel result = await Task.Run(() => DownloadWebsite(site)); cancellationToken.ThrowIfCancellationRequested(); ReportWebsiteInfo(result); Percentage++; } } }
异步中的取消操作,通过CancellationTokenSource 来设置,当_cancellationTokenSource.Cancel()时,
实际开发工作中,经常需要启动一个页面,为防止加载耗时数据时造成的UI阻塞或卡顿,可以采用以下两种方式实现异步加载。
View.xaml
<UserControl x:Class="Test.Views.UploadView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:b="http://schemas.microsoft.com/xaml/behaviors" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> <b:Interaction.Triggers> <b:EventTrigger EventName="Loaded"> <b:InvokeCommandAction Command="{Binding LoadedCommand}"/> </b:EventTrigger> </b:Interaction.Triggers> ...... ...... </UserControl>
ViewModel.cs
public class UploadViewModel : BindableBase
{
public UploadViewModel ()
{
LoadedCommand = new DelegateCommand(LoadAsync);
}
private async void LoadAsync()
{
await Task.Run(async () =>
{
// 一些耗时操作,比如计算、从数据库获取数据等等
}
}
}
我们知道,在构造函数中,是不允许使用awati,构造函数本身也不支持async。
public class AsyncViewModel { public string Text { get; set; } = "I'm a boy"; public AsyncViewModel() { LoadData().Await(CompltedHandle, HandleError); } private void CompltedHandle() { Text = "Success in task"; } private void HandleError(Exception ex) { Text = ex.Message; } private async Task LoadData() { await Task.Delay(1000); Text = "Change in task"; } } public static class TaskExtension { public async static void Await(this Task task, Action compltedCallback, Action<Exception> errorCallback) { try { await task; compltedCallback?.Invoke(); } catch (Exception ex) { errorCallback?.Invoke(ex); } } public async static void Await<T>(this Task<T> task, Action<T> compltedCallback, Action<Exception> errorCallback) { try { T result = await task; compltedCallback?.Invoke(result); } catch (Exception ex) { errorCallback?.Invoke(ex); } } }
可以利用扩展方法的方式来变相实现await效果。
注:Prism框架中已有对应的实现,可以直接使用。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。