C#委托的三种调用示例

554 阅读7分钟

同步调用

运行描述 委托的Invoke方法用来进行同步调用。同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。 运行代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SuanFa
{
    public delegate int  AddDelegate(int i, int j);
    class Delegate_Three
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client application started!\n");
            Thread.CurrentThread.Name = "Main Thread";
            Delegate_Three dt = new Delegate_Three();
            AddDelegate ad = new AddDelegate(dt.Add);
            int result=ad.Invoke(2, 5);
            //int result =dt.Add(2, 5);
            Console.WriteLine("Result: {0}\n", result);
            // 做某些其它的事情,模拟需要执行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("\nPress any key to exit...");
            Console.ReadLine();
        }
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
            Console.WriteLine("Method invoked!");
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <= 2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Console.WriteLine("{0}: Add executed {1} second(s).", 
                    Thread.CurrentThread.Name, i);
            }
            Console.WriteLine("Method complete!");
            return x + y;
        }
    }
}

运行结果

API

  • Thread.Sleep(),它会让执行当前代码的线程暂停一段时间(如果你对线程的概念比较陌生,可以理解为使程序的执行暂停一段时间),以毫秒为单位,比如Thread.Sleep(1000),将会使线程暂停1 秒钟。在上面我使用了它的重载方法,个人觉得使用TimeSpan.FromSeconds(1),可读性更好一些。

  • Thread.CurrentThread.Name,通过这个属性可以设置、获取执行当前代码的线程的名称,值得注意的是这个属性只可以设置一次,如果设置两次,会抛出异常。

  • Thread.IsThreadPoolThread,可以判断执行当前代码的线程是否为线程池中的线程。

同步调用会阻塞线程,如果是要调用一项繁重的工作(如大量IO操作),可能会让程序停顿很长时间,造成糟糕的用户体验,这时候异步调用就很有必要了。

异步调用

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

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SuanFa
{
    public delegate int  AddDelegate(int i, int j);
    class Delegate_Three
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client application started!\n");
            Thread.CurrentThread.Name = "Main Thread";
            Delegate_Three dt = new Delegate_Three();
            AddDelegate ad = new AddDelegate(dt.Add);
            IAsyncResult asyncResult = ad.BeginInvoke(2, 5, null, null);
            // 做某些其它的事情,模拟需要执行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);
            }
            int result = ad.EndInvoke(asyncResult);
            Console.WriteLine("Result: {0}\n", result);
            Console.WriteLine("\nPress any key to exit...");
            Console.ReadLine();
        }
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
            Console.WriteLine("Method invoked!");
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <= 2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Console.WriteLine("{0}: Add executed {1} second(s).", 
                    Thread.CurrentThread.Name, i);
            }
            Console.WriteLine("Method complete!");
            return x + y;
        }
    }
}

运行结果

可以看到,主线程并没有等待,而是直接向下运行了。但是问题依然存在,当主线程运行到EndInvoke时,如果这时调用没有结束(这种情况很可能出现),这时为了等待调用结果,线程依旧会被阻塞。 异步委托,也可以参考如下写法: Action action=(obj)=>method(obj); action.BeginInvoke(obj,ar=>action.EndInvoke(ar),null); 简简单单两句话就可以完成一部操作。

异步回调

运行描述 用回调函数,当调用结束时会自动调用回调函数,解决了为等待调用结果,而让线程依旧被阻塞的局面。 运行代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Remoting.Messaging;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace SuanFa
{
    public delegate int  AddDelegate(int i, int j);
    class Delegate_Three
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Client application started!\n");
            Thread.CurrentThread.Name = "Main Thread";
            Delegate_Three dt = new Delegate_Three();
            AddDelegate ad = new AddDelegate(dt.Add);
            AsyncCallback asy = new AsyncCallback(dt.complete);
            IAsyncResult asyncResult = ad.BeginInvoke(2, 5, asy, "On Complete");
            // 做某些其它的事情,模拟需要执行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("\nPress any key to exit...");
            Console.ReadLine();
        }
        public void complete(IAsyncResult asyncResult)
        {
            //AsyncResult是IAsyncResult的子类
            AsyncResult asy = (AsyncResult)asyncResult;
            AddDelegate ad = (AddDelegate)asy.AsyncDelegate;
            string data = (string)asy.AsyncState;
            int result = ad.EndInvoke(asyncResult);
            Console.WriteLine("{0}: Result, {1}; Data: {2}\n", Thread.CurrentThread.Name, result, data);
        }
        public int Add(int x, int y)
        {
            if (Thread.CurrentThread.IsThreadPoolThread)
            {
                Thread.CurrentThread.Name = "Pool Thread";
            }
            Console.WriteLine("Method invoked!");
            // 执行某些事情,模拟需要执行2 秒钟
            for (int i = 1; i <= 2; i++)
            {
                Thread.Sleep(TimeSpan.FromSeconds(i));
                Console.WriteLine("{0}: Add executed {1} second(s).", 
                    Thread.CurrentThread.Name, i);
            }
            Console.WriteLine("Method complete!");
            return x + y;
        }
    }
}

运行结果

总结

这里有几个值得注意的地方:

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

问题

问题:

  • 为什么Invoke的参数和返回值和AddDelegate委托是一样的呢? 答:Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。 所以Invoke方法的参数和返回值和调用他的委托应该是一致的。

  • IAsyncResult result = ad.BeginInvoke(1,2,null,null); BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行, 返回IAsyncResult 对象(异步的核心). IAsyncResult 简单的说,他存储异步操作的状态信息的一个接口,也可以用他来结束当前异步。 注意:** BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏。**

  • IAsyncResult.AsyncState 属性: 获取用户定义的对象,它限定或包含关于异步操作的信息。

委托解析代码

.class public auto ansi sealed SuanFa.AddDelegate
	extends [mscorlib]System.MulticastDelegate
{
	// Methods
	.method public hidebysig specialname rtspecialname 
		instance void .ctor (
			object 'object',
			native int 'method'
		) runtime managed 
	{
	} // end of method AddDelegate::.ctor

	.method public hidebysig newslot virtual 
		instance int32 Invoke (
			int32 i,
			int32 j
		) runtime managed 
	{
	} // end of method AddDelegate::Invoke

	.method public hidebysig newslot virtual 
		instance class [mscorlib]System.IAsyncResult BeginInvoke (
			int32 i,
			int32 j,
			class [mscorlib]System.AsyncCallback callback,
			object 'object'
		) runtime managed 
	{
	} // end of method AddDelegate::BeginInvoke

	.method public hidebysig newslot virtual 
		instance int32 EndInvoke (
			class [mscorlib]System.IAsyncResult result
		) runtime managed 
	{
	} // end of method AddDelegate::EndInvoke

} // end of class SuanFa.AddDelegate

上面的代码用C#来写为

public delegate int  AddDelegate(int i, int j);

由此可以看出

  • Invoke需要的参数列表与定义委托时的参数列表类型相同,返回值与委托的返回值类型相同。
  • BeginInvoke参数前两个为定义委托时的参数。第三个为AsyncCallback类型,该类型也是一个委托类型,用来定义异步调用执行完毕后的方法。第四个为object 类型,用来存储相关的信息,可以是任何信息。返回值为IAsyncResult类型,可以作为EndInvoke的入口参数
  • EndInvoke参数为IAsyncResult 类型,是一个接口类型。返回值就是委托的返回值。