当前位置:   article > 正文

c# 异步编程async、await和Task_c#异步加载

c#异步加载

场景介绍

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}";
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

当点击“同步”按钮时,会发现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++;
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

在这里插入图片描述
点击异步按钮后,WPF界面可以随意拖动,并且进度条、结果等都是一条一条输出到UI中的。

与第一种同步方式相比,

  • 借助于Task.Run,实现了异步
  • 因为开启了另外一个线程,也就是把耗时的操作放到了另外一个线程去处理,所以不会阻塞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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

第二种异步方式中,每创建一个Task,都会直接await
而在并行方式中,我们先只创建task,然后放到list中,最后await Task.WhenAll(tasks);
这样就可以让所有task以并行的方式同时运行,这样即不阻塞UI,又能更快的执行任务。

Parallel

其实在第一种方式中,如果只是不想阻塞线程,直接写成下面即可,可以实现和第二种异步方式完全相同的效果

 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}";
 }

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果用上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}";
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

取消

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++;
	    }
	}
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

在这里插入图片描述
异步中的取消操作,通过CancellationTokenSource 来设置,当_cancellationTokenSource.Cancel()时,

两种常用的页面异步加载方式

实际开发工作中,经常需要启动一个页面,为防止加载耗时数据时造成的UI阻塞或卡顿,可以采用以下两种方式实现异步加载。

1、利用控件的Loaded事件

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

ViewModel.cs

public class UploadViewModel : BindableBase
{
    public UploadViewModel ()
    {
        LoadedCommand = new DelegateCommand(LoadAsync);
    }

    private async void LoadAsync()
    {
        await Task.Run(async () =>
        {
        	// 一些耗时操作,比如计算、从数据库获取数据等等
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
2、利用构造函数

我们知道,在构造函数中,是不允许使用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);
            }
        }
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

可以利用扩展方法的方式来变相实现await效果。
注:Prism框架中已有对应的实现,可以直接使用。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/104565
推荐阅读
相关标签
  

闽ICP备14008679号