# C#之异步

在异步程序中,程序代码不需要按编写时的顺序严格执行。有时需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好的利用单个线程的能力,需要改变代码的执行顺序,从而解决在性能或用户体验上导致的难以接受的行为。

在C#5.0引入的一个来用来构建异步方法的新特性---async/await,但还有其他形式的异步编程的特性,这些特性是.NET框架的一部分,但没有嵌入C#语言,比如BackgroundWorker类和.NET任务并行库,两者均通过新建线程来实现异步。

async/await特性的结构

异步的方法在处理完之前就返回到调用方法。C#的async/await特性可以创建并使用异步方法。该特性有三部分组成。

  • 调用方法:该方法调用异步方法,然后在异步方法(可能在相同的线程,也可能在不同的线程)执行其任务的时候继续执行。
  • 异步方法,该方法异步执行其工作,然后立即返回到调用方法。
  • await表达式:用于异步方法内部,指明需要异步执行的任务,一个异步方法可以包含任意多个await表达式,不过如果一个都不包含的话编译器会发出警告

什么是异步方法

在语法上,异步方法具有如下特点:

  • 方法头中包含async方法修饰,且必须出现在返回类型之前,async关键字是一个上下文关键字,它只是标识该方法包含了一个或多个await表达式,也就是说async本身并不能创建任何异步操作。
  • 方法中包含一个或多个await表达式,用以表示可以异步完成的任务
  • 返回类型只有三种:Task<T>(该类型,异步方法必须有相应的return T obj与之呼应);Task(该方法不能有return 语句),且在调用方法控制流中可以设置task.Wait()方法来等待异步方法结束,如果没有的话,调用方法的控制流(主程序)结束 后,异步方法将被迫结束。
  • 异步方法的参数可以为任意数量的任意类型,但不能为out或ref参数
  • 按照约定,异步方法的名称应以Async为后缀。
  • 除了方法外,Lambda表达式和匿名方法也可以作为异步的对象.

(1)返回类型是Task<T>的异步方法

任何返回Task类型的异步方法的返回值,必须为T类型或可以隐式转换为T的类型。

namespace ConsoleApp
{
    static class DoStuff
    {
        public  static async  Task<int> FindSeriesSum() //返回类型是Task<int>与异步方法的return 1相呼应
        {
            Console.WriteLine("Entering into asyn function");
            await Task.Run(WriteN);
            Console.WriteLine("Negative over...");
            await Task.Run(WriteP);
            Console.WriteLine("Positve over...`");
            return 1;
        }
        public static void WriteN()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("-");
            }
        }
        public static void WriteP()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("+");
            }
        }
        
    }
   
    class Program
    {
        public static void Main(string[] args)
        {
            Task<int> task=DoStuff.FindSeriesSum();
            Console.WriteLine("wait asyn function...");
            var t = task.Result;
            Console.WriteLine("all over...");
            Console.WriteLine(t);
        }

    }
}

image-20211110224533518

可以发现,当遇到第一个await,程序直接返回调用方法的控制流(打印出wait async function,此后便在task.Wait处等待异步方法的结束),同时开始执行await后的任务,执行完第一个await任务,在异步控制流中继续前进,打印完Negative over...后,又碰到第二个await任务,返回到调用方法的控制流,此时,实际上调用方法的控制流处于等待状态,在task.Result处,同时开始执行第二个任务,直到执行完毕后退出(打印Positive over...),调用方法的控制流继续前进,打印all over...,整个程序结束。

当去掉task.Result后,发现调用程序结束,异步方法也被迫结束。

 public static void Main(string[] args)
        {
            Task<int> task=DoStuff.FindSeriesSum();
            Console.WriteLine("wait asyn function...");
            Console.WriteLine("all over...");
        }

image-20211110231742792

(2)返回类型是Task的异步方法

namespace ConsoleApp
{
    static class DoStuff
    {
        public  static async  Task FindSeriesSum()//返回值是Task,这意味着在调用方法的控制流中必须使用task.Wait()方法来使调用方法的控制流在此处等异步方法执行完毕,再继续前进。
        {
            Console.WriteLine("Entering into asyn function");
            await Task.Run(WriteN);
            Console.WriteLine("Negative over...");
            await Task.Run(WriteP);
            Console.WriteLine("Positve over...`");
            return;//可有可无,若有的话,像这样即使异步方法出现了return,也不会返回任何东西,它只是退出了。
        }
        public static void WriteN()
        {
            for(int i = 0; i < 2000; i++)
            {
                Console.Write("-");
            }
        }
        public static void WriteP()
        {
            for(int i = 0; i < 2000; i++)
            {
                Console.Write("+");
            }
        }
        
    }
   
    class Program
    {
        public static void Main(string[] args)
        {
            var sometask=DoStuff.FindSeriesSum();
            Console.WriteLine("wait asyn function...");
            sometask.Wait();
            Console.WriteLine("all over...");
        }
        private static void CountBig(int p)
        {
            for (int i = 0; i < p; i++) ;
        }
    }
}

image-20211110223147735

可以发现,当遇到第一个await,程序直接返回调用方法的控制流(打印出wait async function,此后便在task.Wait处等待异步方法的结束),同时开始执行await后的任务,执行完第一个await任务,在异步控制流中继续前进,打印完Negative over...后,又碰到第二个await任务,返回到调用方法的控制流,此时,实际上调用方法的控制流处于等待状态,在someTask.Wait()处,同时开始执行第二个任务,直到执行完毕后退出(打印Positive over...),调用方法的控制流继续前进,打印all over...,整个程序结束。

同样地,当去掉task.Wait,会发现随着调用方法的控制流结束,异步方法的控制流也被迫结束。

using System;
using System.Threading;
using System.Threading.Tasks;

namespace threadDemo
{
    static class DoStuff
    {
        public static async Task FindSeriesSum()//返回值是Task,这意味着在调用方法的控制流中必须使用task.Wait()方法来使调用方法的控制流在此处等异步方法执行完毕,再继续前进。
        {
            Console.WriteLine("Entering into asyn function");
            await Task.Run(WriteN);
            Console.WriteLine("Negative over...");
            await Task.Run(WriteP);
            Console.WriteLine("Positve over...`");
            return;//可有可无,若有的话,像这样即使异步方法出现了return,也不会返回任何东西,它只是退出了。
        }
        public static void WriteN()
        {
            for (int i = 0; i < 2000; i++)
            {
                Console.Write("-");
            }
        }
        public static void WriteP()
        {
            for (int i = 0; i < 2000; i++)
            {
                Console.Write("+");
            }
        }

    }

    class Program
    {
        public static void Main(string[] args)
        {
            var sometask = DoStuff.FindSeriesSum();
            Console.WriteLine("wait asyn function...");
            for (int i = 0; i < 200; i++)
                Console.Write(i);
            sometask.Wait();
            Console.WriteLine("all over...");
        }
        private static void CountBig(int p)
        {
            for (int i = 0; i < p; i++) ;
        }
    }
}


output:

Entering into asyn function
wait asyn function...
01234567891011121314151617181920--------2122----2324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Negative over...
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Positve over...`
all over...

(3)返回类型是void的异步方法,即fire and forget模式

namespace ConsoleApp
{
    static class DoStuff
    {
        public  static async  void FindSeriesSum() //返回类型是void,所以是fire and forget模式,这意味着,调用方法的控制流结束后,异步方法的控制流也必须停止。
        {
            Console.WriteLine("Entering into asyn function");
            await Task.Run(WriteN);
            Console.WriteLine("Negative over...");
            await Task.Run(WriteP);
            Console.WriteLine("Positve over...`");
            return;//可有可无,若有,像这样,它并不返回任何东西,只是退出了。
        }
        public static void WriteN()
        {
            for(int i = 0; i < 2000; i++)
            {
                Console.Write("-");
            }
        }
        public static void WriteP()
        {
            for(int i = 0; i < 2000; i++)
            {
                Console.Write("+");
            }
        }
        
    }
   
    class Program
    {
        public static void Main(string[] args)
        {
            DoStuff.FindSeriesSum();
            Console.WriteLine("wait asyn function...");
            Console.WriteLine("all over...");
        }

    }
}

image-20211110223903676

可以看到,异步方法中的控制流在调用方法的控制流结束时,被打断。

class Program
   {
        public static async Task DoRun()
        {
            Console.WriteLine("Enter DoRun...");
            await Task.Run(WriteP);
            Console.WriteLine("Now WriteN...");
            await Task.Run(WriteN);
        }
        public static void WriteP()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("+");
            }
        }
        public static void WriteN()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("-");
            }
        }
       
        static void Main()
        {
            Console.WriteLine("Test begin...");
            Task task = DoRun();
            Console.WriteLine("Sleep..");
            Thread.Sleep(5);
            Console.WriteLine("After sleep...");
            task.Wait();
            Console.WriteLine("Over...");
        }
    }

output:

Test begin...
Enter DoRun...
+++++Sleep..
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Now WriteN...
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------After sleep...
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------Over...

await表达式

await表达式指定了一个异步执行的任务。语法非常简单,就是await关键字,后面跟上一个awaitable类型的实例,万幸的是,我们并不需要构建自己的awaitable,我们可以使用Task类,Task类就是awaitable类型,这也能满足我们大多数需求。在.NET4.5后,还可以用Task<T>类型对象,它也是awaitable类型,用以和await关键字搭配,组成用以异步执行的任务。

而创建一个Task类,最简单的就是使用Task.Run方法来创建一个Task,其中Task.Run的重载的返回类型和签名如下表:

返回类型 签名
Task Run(Action action)
Task Run(Action action,CancellationToken token)
Task Run(Func function)
Task Run(Func function,CancellationToken token)
Task Run(Func function)
Task Run(Func function)
Task Run(Func<Task> function)
Task Run(Func<Task> function, CancellationTokeen token)
        public static void Main(string[] args)
        {
            Console.WriteLine("Test begin...");
            Task task = DoWorkAsyn();
            Console.WriteLine("wait async function");
            task.Wait();
            Console.WriteLine("Press any key to exit");
            Console.Read();
        }
        public static async Task DoWorkAsyn()
        {
            await Task.Run(()=>Console.WriteLine(5.ToString()));
            Console.WriteLine((await Task.Run(()=>6)).ToString());
            await Task.Run(() => Task.Run(() => Console.WriteLine(7.ToString())));
            int value = await Task.Run(() => Task.Run(() => 8));
            Console.WriteLine(value);
        }

output:

Test begin...
wait async function
5
6
7
8
Press any key to exit
  • 取消一个异步操作

System.Threading.Tasks命名控件有两个类是为此目的而设计的:CancellationTokenCancellationTokenSource

   class Program
    {
        public static void Main(string[] args)
        {
            CancellationTokenSource cts = new CancellationTokenSource();
            CancellationToken token = cts.Token;
            MyClass mc = new MyClass();
            
            Task t = mc.RunAsync(token);
            //Thread.Sleep(3000);
            //cts.Cancel();

            t.Wait();
            Console.WriteLine("Was Cancelled:{0}", token.IsCancellationRequested);
        }
    }
    class MyClass
    {
        public async Task RunAsync(CancellationToken ct)
        {
            Console.WriteLine("Enter in RunAsync");
            if (ct.IsCancellationRequested) return;
            await Task.Run(() => CycleMethod(ct), ct);
        }
        void CycleMethod(CancellationToken ct)
        {
            Console.WriteLine("Starting CycleMethod");
            const int max = 5;
            for(int i = 0; i < max; i++)
            {
                if (ct.IsCancellationRequested)
                    return;
                Thread.Sleep(1000);
                Console.WriteLine("{0} of {1} iterations completed", i + 1, max);
            }
        }
    }

output:

Enter in RunAsync
Starting CycleMethod
1 of 5 iterations completed
2 of 5 iterations completed
3 of 5 iterations completed
4 of 5 iterations completed
5 of 5 iterations completed
Was Cancelled:False

取消注释后:

output:

Enter in RunAsync
Starting CycleMethod
1 of 5 iterations completed
2 of 5 iterations completed
3 of 5 iterations completed
Was Cancelled:True
  • 在调用方法中同步的等待任务
class Program
   {
        public static async Task DoRun(Action act)
        {
            Console.WriteLine("Enter DoRun...");
            await Task.Run(act);
        }
        public static void WriteP()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("+");
            }
        }
        public static void WriteN()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("-");
            }
        }
       
        static void Main()
        {
            Task task1 = DoRun(WriteP);
            Task task2 = DoRun(WriteN);
            Task.WaitAll(task1, task2);
            Console.WriteLine("Task 1:{0} Finished", task1.IsCompleted ? "" : "Not");
            Console.WriteLine("Task 2:{0} Finished", task2.IsCompleted ? "" : "Not");
        }
    }

output:

Enter DoRun...
++++++++++++++Enter DoRun...
+++++++++++---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Task 1: Finished
Task 2: Finished

将上面的Task.WaitAll改为Task.WaitAny,output:

Enter DoRun...
Enter DoRun...
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------Task 1: Finished
Task 2:Not Finished
------------------------------------------------
  • 在异步方法中异步地等待任务
class Program
   {
        public static async Task DoRun()
        {
            Console.WriteLine("Enter DoRun...");
            Task task1 = Task.Run(WriteP);
            Task task2 = Task.Run(WriteN);
            await Task.WhenAll(new List<Task> { task1, task2 });
            Console.WriteLine("Task 1:{0} Finished", task1.IsCompleted ? "" : "Not");
            Console.WriteLine("Task 2:{0} Finished", task2.IsCompleted ? "" : "Not");
        }
        public static void WriteP()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("+");
            }
        }
        public static void WriteN()
        {
            for(int i = 0; i < 1000; i++)
            {
                Console.Write("-");
            }
        }
       
        static void Main()
        {
            Console.WriteLine("Test begin...");
            Task tasks = DoRun();
            tasks.Wait();
            Console.WriteLine("Over...");
            
        }
       
    }

output:

Test begin...
Enter DoRun...
++++++++++++++++++++++++++++++++++----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++Task 1: Finished
Task 2: Finished
Over...

在异步方法中,异步的等待,直到两个任务都完成了,才在异步方法中继续进行下一步,因此打印完毕+-后,最后在异步中给出两个任务的状态。

        static void Main()
        {
            Task t = BadAsyn();
            t.Wait();
            Console.WriteLine("Task status:{0}", t.Status);
            Console.WriteLine("Task IsFaulted:{0}", t.IsFaulted);
            
        }
        public static async Task BadAsyn()
        {
            try
            {
                await Task.Run(() => throw new Exception());
            }
            catch
            {
                Console.WriteLine("Exception in BadAsync");
            }
        }

output:

Exception in BadAsync
Task status:RanToCompletion
Task IsFaulted:False
  • Task.Delay()
static void Main()
        {
            Console.WriteLine("Test begins...");
            TestAsyn();
            Console.WriteLine("Test ends...");
            Console.ReadLine();//阻塞调用方法所在的主线程控制流
        }
        public static async void TestAsyn()
        {
            Console.WriteLine("Enter testAsyn");
            await Task.Delay(500);//异步方法控制流中的“暂停”,不影响调用方法主线程控制流
            Console.WriteLine("After Delay...");
        }

output:

Test begins...
Enter testAsyn
Test ends...
After Delay...

在GUI中执行异步操作

实际上,异步方法在GUI程序上非常有用,因为GUI程序在设计上要求所有的显示变化都必须在主GUI线程中完成,如点击按钮,展示标签,移动窗体等(各种消息),Windows是通过消息来实现这一点的,消息被放入由消息泵管理的消息队列中。

消息泵从队列中取出一条消息,并调用它的处理程序代码,当处理程序代码完成后,消息泵获取下一个消息,并循环此过程。 也正因为此,处理程序就必须执行时间要很短,这样才不至于挂起阻碍其他消息的处理,否则消息队列中的消息产生积压,程序失去相应响应。

<StackPanel>
        <Label Name="lblStatus" Margin="10,5,10,0">Not Doing Anything</Label>
        <Button x:Name="btnDoStuff" Content="Do Stuff" HorizontalAlignment="Left" Margin="10,5" Padding="5,2" Click="btnDoStuff_Click"/>
    </StackPanel>
  public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            btnDoStuff.IsEnabled = false;
            lblStatus.Content = "Doing Stuff";
            //Thread.Sleep(4000);
            await Task.Delay(4000);
            lblStatus.Content = "Not Doing Anything";
            btnDoStuff.IsEnabled = true;
        }
    }

image-20211111232517053

image-20211111232544772

使用async/await可以将该点击消息的前两条消息压入队列,然后将自己从处理器“摘下”,4秒后将自己剩余部分压入队列。

BackgroundWorker

async/awaiter特性更适合那些需要在后台完成的不想关的小任务,但有时候,可能需要另建一个线程,在后台持续运行以完成某项工作,并不时地与主线程进行通信,而这正是BackgroundWorker所要做的。

BackgroundWorker的属性有WokerReportsProgressWokerSupportsCancellationIsBusy,CancellationPending,方法有RunWorkerAsync()CancelAsync()ReportsProgress(),事件有DoWorkProgressChangedRunWorkerCompleted

其中:

WorkerReportsProgress用于设置BackgroundWorker是否将它的进度汇报给主线程,WorkerSupportsCancellation用于设置是否允许主线程取消BackgroundWorker的工作。

RunWorkerAsync方法负责在主线程中启动BackgroundWorker,同时触发DoWork事件,而在DoWork事件中,可以添加需要完成的任务的代码,如果要给主线程汇报进度,可调用ReportsProgress()方法,而ReportsProgress()方法又触发ProgressChanged事件,后台程序正在工作,则其IsBusy属性就是true。

如果允许主线程取消后台线程任务,主线程调用CancelAsync方法,该方法会使BackgroundWorkerIsCancellationPending属性为true。

事件处理程序的委托如下:

void DoWorkEventHandler(object sender,DoWorkEventArgs e);
void ProgressChangedEventHandler(object sender,ProgressChangedEventArgs e);
void RunWorkerCompletedEventHandler(object sender,RunWorkerCompletedEventArgs e);

这些事件处理程序的sender自然都是发起事件的BackgroundWorker,关于args的说明参考微软文档:

  • DoworkEventArgs
image-20211113100246831
  • ProgressChangedEventArgs

ProgressPercentage是传给主线程的后台线程任务完成的进度值,也是ReportsProcess的参数。

image-20211113100638603

RunWorkerCompletedEventArgs

image-20211113100900176 image-20211113101036282

例子:

    <StackPanel>
        <ProgressBar x:Name="progressBar" Height="20" Width="200" Margin="10"/>
        <Button x:Name="btnProcess" Width="100" Click="btnDoStuff_Click">Process</Button>
        <Button x:Name="btnCancel" Width="100" Click="btnCancel_Click">Cancel</Button>
    </StackPanel>
 public partial class MainWindow : Window
    {
        BackgroundWorker bgworker = new BackgroundWorker();
        public MainWindow()
        {
            InitializeComponent();
            bgworker.WorkerReportsProgress = true;//允许向主线程汇报进度
            bgworker.WorkerSupportsCancellation = true;//允许主线程取消后台任务
            bgworker.DoWork += DoWork_Handler;//注册DoWork事件
            bgworker.ProgressChanged += ProgressChanged_Handler;//注册ProgressChanged事件
            bgworker.RunWorkerCompleted += Bgworker_RunWorkerCompleted;//注册RunWorkCompleted事件
        }

        private void Bgworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //完成后的操作,不管是正常结束还是被取消的
            progressBar.Value = 0;
            if (e.Cancelled)
                MessageBox.Show("被取消", "有内鬼,任务取消");
            else
                MessageBox.Show("完成", "任务完成");
        }

        private void btnDoStuff_Click(object sender, RoutedEventArgs e)
        {
            if (!bgworker.IsBusy)
            {
                bgworker.RunWorkerAsync(); //触发Dowork事件
            }
        }
        private void DoWork_Handler(object sender,DoWorkEventArgs e)
        {
            BackgroundWorker worker = sender as BackgroundWorker;
            for(int i = 1; i <= 10; i++)
            {
                if (worker.CancellationPending) //不断检查Cancellation属性,如果true,则将e的Cancel属性
                {                              //设置为true,这样RunCompletedEventArgs的Cancelled属性就也被设定为true
                    e.Cancel = true;
                    break;
                }
                else
                {
                    worker.ReportProgress(i * 10);//否则,向主线程汇报进度,触发ProgressChanged事件,参数传给该事件的
                                                  //ProgressChangedEventArgs的ProgressPercentage属性
                    Thread.Sleep(500);//模拟time-consuming 任务
                }
            }
        }
        private void ProgressChanged_Handler(object sender, ProgressChangedEventArgs e)
        {
            progressBar.Value = e.ProgressPercentage;//将e的ProgressPerceentage值传入进度条
        }
        
        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            bgworker.CancelAsync();
        }
    }
image-20211113102805820 image-20211113102836094 image-20211113102909225
posted @ 2021-11-13 10:33  JohnYang819  阅读(731)  评论(0编辑  收藏  举报