• 关于 C#异步方法的使用


    委托和方法的异步调用

    本人对原作进行了简单的修改,大部分都与原文相同

    也许业内很多高不成低不就的程序员都会对一些知识点会有些迷惑,原因是平常工作用的少,所以也就决定了你对这个事物的了解程度。今天就来看看C#中异步方法的使用。希望对大家有所帮助。

    --原文

    通常情况下,如果需要异步执行一个耗时的操作,我们会新起一个线程,然后让这个线程去执行代码。但是对于每一个异步调用都通过创建线程来进行操作显 然会对性能产生一定的影响,同时操作也相对繁琐一些。.Net中可以通过委托进行方法的异步调用,就是说客户端在异步调用方法时,本身并不会因为方法的调 用而中断,而是从线程池中抓取一个线程去执行该方法,自身线程(主线程)在完成抓取线程这一过程之后,继续执行下面的代码,这样就实现了代码的并行执行。 使用线程池的好处就是避免了频繁进行异步调用时创建、销毁线程的开销。

    如同上面所示,当我们在委托对象上调用BeginInvoke()时,便进行了一个异步的方法调用。而在这种情况下使用异步编程时,就需要进行更多的控制,比如当异步执行方法的方法结束时通知客户端、返回异步执行方法的返回值等。本节就对BeginInvoke()方法、EndInvoke()方法和其相关的IAysncResult做一个简单的介绍。

    NOTE:讨论在客户端程序中异步地调用方法。

    我们看这样一段代码,它演示了不使用异步调用的通常情况:

     1 class Program7 {
     2     static void Main(string[] args) {
     3 
     4         Console.WriteLine("Client application started! ");
     5         Thread.CurrentThread.Name = "Main Thread";
     6 
     7         Calculator cal = new Calculator();
     8         int result = cal.Add(2, 5);
     9         Console.WriteLine("Result: {0} ", result);
    10        
    11         // 做某些其它的事情,模拟需要执行3秒钟
    12         for (int i = 1; i <= 3; i++) {
    13             Thread.Sleep(TimeSpan.FromSeconds(1));
    14             Console.WriteLine("{0}: Client executed {1} second(s).",
    15                 Thread.CurrentThread.Name, i); 
    16         }
    17 
    18         Console.WriteLine(" Press any key to exit...");
    19         Console.ReadKey();
    20     }
    21 }
    22 
    23 public class Calculator {
    24     public int Add(int x, int y) {
    25         if (Thread.CurrentThread.IsThreadPoolThread) {
    26             Thread.CurrentThread.Name = "Pool Thread";
    27         }
    28         Console.WriteLine("Method invoked!");          
    29 
    30         // 执行某些事情,模拟需要执行2秒钟
    31         for (int i = 1; i <= 2; i++) {
    32             Thread.Sleep(TimeSpan.FromSeconds(1));
    33             Console.WriteLine("{0}: Add executed {1} second(s).",
    34                 Thread.CurrentThread.Name, i); 
    35         }
    36         Console.WriteLine("Method complete!");
    37         return x + y;
    38     }
    39 } 

    上面代码有几个关于对于线程的操作,如果不了解可以看一下下面的说明,如果你已经了解可以直接跳过:

    • Thread.Sleep(), 它会让执行当前代码的线程暂停一段时间(如果你对线程的概念比较陌生,可以理解为使程序的执行暂停一段时间),以毫秒为单位,比如 Thread.Sleep(1000),将会使线程暂停1秒钟。在上面我使用了它的重载方法,个人觉得使用 TimeSpan.FromSeconds(1),可读性更好一些。
    • Thread.CurrentThread.Name,通过这个属性可以设置、获取执行当前代码的线程的名称,值得注意的是这个属性只可以设置一次,如果设置两次,会抛出异常。
    • Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程。

    通 过这几个方法和属性,有助于我们更好地调试异步调用方法。上面代码中除了加入了一些对线程的操作以外再没有什么特别之处。我们建了一个 Calculator类,它只有一个Add方法,我们模拟了这个方法需要执行2秒钟时间,并且每隔一秒进行一次输出。而在客户端程序中,我们使用 result变量保存了方法的返回值并进行了打印。随后,我们再次模拟了客户端程序接下来的操作需要执行2秒钟时间。运行这段程序,会产生下面的输出:

    Client application started!
    
    Method invoked!
    Main Thread: Add executed 1 second(s).
    Main Thread: Add executed 2 second(s).
    Method complete!
    Result: 7
    
    Main Thread: Client executed 1 second(s).
    Main Thread: Client executed 2 second(s).
    Main Thread: Client executed 3 second(s).
    
    Press any key to exit...

    如果你确实执行了这段代码,会看到这些输出并不是一瞬间输出的,而是执行了大概5秒钟的时间,因为线程是串行执行的,所以在执行完Add()方法之后才会继续客户端剩下的代码。

    接下来我们定义一个AddDelegate委托,并使用BeginInvoke()方法来异步地调用它。在上面已经介绍过,BeginInvoke()除了 最后两个参数为AsyncCallback类型和Object类型以外,前面的参数类型和个数与委托的方法定义相同。另外BeginInvoke()方法返回了一个实现了IAsyncResult接口的对象(实际上就是一个AsyncResult(System.Runtime.Remoting.Messaging命名空间里)类型实例,注意这里IAsyncResult和 AysncResult是不同的)。

    AsyncResult的用途有这么几个:传递参数,它 包含了对调用了BeginInvoke()的委托的引用;它还包含了BeginInvoke()的最后一个Object类型的参数;它可以鉴别出是哪个方 法的哪一次调用,因为通过同一个委托变量可以对同一个方法调用多次。

    EndInvoke()方法接受IAsyncResult类型的对象 (以及ref和out类型参数,这里不讨论了,对它们的处理和返回值类似),所以在调用BeginInvoke()之后,我们需要保留 IAsyncResult,以便在调用EndInvoke()时进行传递。这里最重要的就是EndInvoke()方法的返回值,它就是方法的返回值。除 此以外,当客户端调用EndInvoke()时,如果异步调用的方法没有执行完毕,则会中断当前线程而去等待该方法,只有当异步方法执行完毕后才会继续执 行后面的代码。所以在调用完BeginInvoke()后立即执行EndInvoke()是没有任何意义的。我们通常在尽可能早的时候调用 BeginInvoke(),然后在需要方法的返回值的时候再去调用EndInvoke(),或者是根据情况在晚些时候调用。说了这么多,我们现在看一下 使用异步调用改写后上面的代码吧:

     1 public delegate int AddDelegate(int x, int y);
     2 
     3 class Program8 {   
     4 
     5     static void Main(string[] args) {
     6 
     7         Console.WriteLine("Client application started! ");
     8         Thread.CurrentThread.Name = "Main Thread";
     9                    
    10         Calculator cal = new Calculator();
    11         AddDelegate del = new AddDelegate(cal.Add);
    12         IAsyncResult asyncResult = del.BeginInvoke(2,5,null,null);  // 异步调用方法
    13 
    14         // 做某些其它的事情,模拟需要执行3秒钟
    15         for (int i = 1; i <= 3; i++) {
    16             Thread.Sleep(TimeSpan.FromSeconds(i));
    17             Console.WriteLine("{0}: Client executed {1} second(s).",
    18                 Thread.CurrentThread.Name, i);
    19         }
    20 
    21         int rtn = del.EndInvoke(asyncResult);
    22         Console.WriteLine("Result: {0} ", rtn);
    23 
    24         Console.WriteLine(" Press any key to exit...");
    25         Console.ReadKey();
    26     }
    27 }
    28 
    29 public class Calculator { /* 与上面同,略 */}

    此时的输出为:

    Client application started!
    
    Method invoked!
    Main Thread: Client executed 1 second(s).
    Pool Thread: Add executed 1 second(s).
    Main Thread: Client executed 2 second(s).
    Pool Thread: Add executed 2 second(s).
    Method complete!
    Main Thread: Client executed 3 second(s).
    Result: 7
    
    
    Press any key to exit...

    现在执行完这段代码只需要3秒钟时间,两个for循环所产生的输出交替进行,这也说明了这两段代码并行执行的情况。可以看到Add()方法是由线程 池中的线程在执行,因为Thread.CurrentThread.IsThreadPoolThread返回了True,同时我们对该线程命名为了 Pool Thread。另外我们可以看到通过EndInvoke()方法得到了返回值。

    有时候,我们可能会将获得返回值的操作放到另一段 代码或者客户端去执行,而不是向上面那样直接写在BeginInvoke()的后面。比如说我们在Program中新建一个方法GetReturn(), 此时可以通过AsyncResult的AsyncDelegate获得del委托对象,然后再在其上调用EndInvoke()方法,这也说明了 AsyncResult可以唯一的获取到与它相关的调用了的方法(或者也可以理解成委托对象)。所以上面获取返回值的代码也可以改写成这样:

    static int GetReturn(IAsyncResult asyncResult) {
        AsyncResult result = (AsyncResult)asyncResult;
        AddDelegate del = (AddDelegate)result.AsyncDelegate;
        int rtn = del.EndInvoke(asyncResult);
        return rtn;
    }

    然后再将int rtn = del.EndInvoke(asyncResult);语句改为int rtn = GetReturn(asyncResult);。注意上面IAsyncResult要转换为实际的类型AsyncResult才能访问 AsyncDelegate属性,因为它没有包含在IAsyncResult接口的定义中。

    BeginInvoke的另外两个参数分别是AsyncCallback和Object类型,其中AsyncCallback是一个委托类型,它用于方法的回调,即是说当异步方法执行完毕时自动进行调用的方法。它的定义为:

    public delegate void AsyncCallback(IAsyncResult ar);

    Object类型用于传递任何你想要的数值,它可以通过IAsyncResult的AsyncState属性获得。下面我们将获取方法返回值、打印返回值的操作放到了OnAddComplete()回调方法中:

    public delegate int AddDelegate(int x, int y);
    
    class Program9 {
    
        static void Main(string[] args) {
    
            Console.WriteLine("Client application started! ");
            Thread.CurrentThread.Name = "Main Thread";
    
            Calculator cal = new Calculator();
            AddDelegate del = new AddDelegate(cal.Add);
            string data = "Any data you want to pass.";
            AsyncCallback callBack = new AsyncCallback(OnAddComplete);
            del.BeginInvoke(2, 5, callBack, data);      // 异步调用方法
    
            // 做某些其它的事情,模拟需要执行3秒钟
            for (int i = 1; i <= 3; i++) {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Console.WriteLine("{0}: Client executed {1} second(s).",
                    Thread.CurrentThread.Name, i);
            }
    
            Console.WriteLine(" Press any key to exit...");
            Console.ReadKey();
        }
    
        static void OnAddComplete(IAsyncResult asyncResult) {
            AsyncResult result = (AsyncResult)asyncResult;
            AddDelegate del = (AddDelegate)result.AsyncDelegate;
            string data = (string)asyncResult.AsyncState;
    
            int rtn = del.EndInvoke(asyncResult);
            Console.WriteLine("{0}: Result, {1}; Data: {2} ",
                Thread.CurrentThread.Name, rtn, data);
        }
    }
    public class Calculator { /* 与上面同,略 */}

    产生的输出为:

    Client application started!
    
    Method invoked!
    Main Thread: Client executed 1 second(s).
    Pool Thread: Add executed 1 second(s).
    Main Thread: Client executed 2 second(s).
    Pool Thread: Add executed 2 second(s).
    Method complete!
    Pool Thread: Result, 7; Data: Any data you want to pass.
    
    Main Thread: Client executed 3 second(s).
    
    Press any key to exit...

    这里有几个值得注意的地方:1、我们在调用BeginInvoke()后不再需要保存IAysncResult了,因为AysncCallback委托将该对象定义在了回调方法的参数列表中;2、我们在OnAddComplete()方法中获得了调用BeginInvoke()时最后一个参数传递的值,字 符串“Any data you want to pass”;3、执行回调方法的线程并非客户端线程Main Thread,而是来自线程池中的线程Pool Thread。另外如前面所说,在调用EndInvoke()时有可能会抛出异常,所以在应该将它放到try/catch块中,这里我就不再示范了。

    结语:这篇文章简单的描述了异步委托方法的使用和说明,本文是从原作者内容中摘抄而来,做了很少的改动,望见谅。

    感谢阅读,希望对你能有所帮助。


    原文地址:http://www.cnblogs.com/JimmyZhang/archive/2008/08/22/1274342.html

  • 相关阅读:
    iOS NSUserDefaults 存储可变数组问题
    iOS之[文件下载 / 大文件下载 / 断点下载]
    macOS 新手开发:第 2 部分
    iOS 游戏素材
    iOS 动画
    macOS 开发
    iOS 之访问权限以及跳转到系统界面
    蓝桥杯—ALGO-18 单词接龙(DFS)
    蓝桥杯—ALGO-12 幂方分解(递归递推)
    蓝桥杯—ALGO-131 Beaver's Calculator
  • 原文地址:https://www.cnblogs.com/crith/p/2614430.html
Copyright © 2020-2023  润新知