65.9K
CodeProject 正在变化。 阅读更多。
Home

C# 中的委托 -尝试深入了解 -第二部分

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (34投票s)

2010年10月4日

CPOL

6分钟阅读

viewsIcon

56197

如果需要,异步调用委托

引言

这是我打算深入探讨该主题的系列文章的第二部分。如果您没有阅读第一部分,我强烈建议您阅读。在此处 阅读文章
在第二部分中,我想讨论调用委托所表示的函数的不同方法。主要部分实际上是何时以及如何异步调用委托。使用异步调用被认为是高级技术,因为它涉及线程。我希望在您阅读完本文后,您将完全准备好在应用程序中使用这些调用。我想采用一种不同的方法来解释委托调用。我将尝试虚构一个生活情景并将其应用于编程环境。我认为这将更容易理解这个问题,并意识到使用委托并没有什么神秘之处;一切都有其目标和目的。

生活中的问题

今晚我要招待一些朋友。我有一份在我朋友来之前需要做的事情清单。我希望有条理,这是我创建的清单。

  1. 打扫房间
  2. 做一些食物
  3. 摆桌子
  4. 准备好(洗澡、穿衣等)

好吧,我不认为这些事情很难做,但我不是厨师。所以我需要一些专业帮助。
我可以打电话订餐。我订餐的餐厅有一些很棒的政策。如果您愿意,他们可以在您的公寓里准备食物。他们会送餐并使用您的厨房做饭。您实际上可以看到正在发生的事情。另一种选择是他们在餐厅提前做好,然后送餐。

程序模型

现在我们尝试将这种情况放入编程中。
首先,我将创建一个名为 Restaurant 的类,其中包含一个名为 MakeFood static 函数,该函数接收一个 string 类型的订单并返回一个 string ,表明订单已完成。这个过程可能需要很长时间,这就是我放置 Thread.Sleep 方法的原因。代码如下:

/// <summary>
/// Just a restaurant
/// </summary>
public class Restaurant
{
	/// <summary>
	/// Static function excepts an order and delivers some food
	/// </summary>
	/// <param name="order">
	/// <returns>
	public static string MakeFood(string order)
	{
		//register start:
		Console.WriteLine("Making {0} started at {1}", order, 
				DateTime.Now.ToLongTimeString());

		//food preparation:
		Thread.Sleep(4000);

		//register finish
		Console.WriteLine("Making {0} finished at {1}", order, 
				DateTime.Now.ToLongTimeString());

		//deliver:
		return order.ToUpper() + " made";
	}
}

下一个 PartyHost 类有几个 public 函数,它们实际上是在准备聚会时要完成的任务。
每个函数都通过控制台注册开始和结束时间。我还放置了一些时间延迟来模拟现实生活中的延迟。
该类没有 MakeFood 函数,而是使用一个名为 Restaurant MakeFood 函数的委托。
在现实生活中,这个委托就像用来从餐厅订餐的电话,这就是我这样命名委托的原因。

public delegate string OrderHandle(string s); 
/// <summary>
/// Party Host
/// </summary>
public class PartyHost
{
	/// <summary>
	/// Person Name
	/// </summary>
	public string Name { get; private set; }

	/// <summary>
	/// constructor
	/// </summary>
	/// <param name="name">
	public PartyHost(string name)
	{
		this.Name = name;
	}

	/// <summary>
	/// Clean up place for party
	/// </summary>
	public void CleanUpPlace()
	{
		//register start:
		Console.WriteLine("Cleaning  started at {0}", 
				DateTime.Now.ToLongTimeString());

		//cleaning:
		Thread.Sleep(3000);

		//register end:
		Console.WriteLine("Cleaning  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Set up furniture for the party
	/// </summary>
	public void SetupFurniture()
	{
		//register start:
		Console.WriteLine("Furniture setup  started at {0}" , 
				DateTime.Now.ToLongTimeString());

		//setting up:
		Thread.Sleep(2000);

		//register end:
		Console.WriteLine("Furniture setup  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Take your time
	/// </summary>
	public void TakeBathAndDressUp()
	{
		//register start:
		Console.WriteLine("TakeBathAndDressUp  started at {0}", 
				DateTime.Now.ToLongTimeString());

		//having fun:
		Thread.Sleep(2000);

		//register end:
		Console.WriteLine("TakeBathAndDressUp  finished at {0}", 
				DateTime.Now.ToLongTimeString());
	}

	/// <summary>
	/// Get restaurant's phone number
	/// </summary>
	public OrderHandle GetRestaurantPhoneNumber()
	{
		//find the restaurant's phone number in the phone book:
		OrderHandle phone = new OrderHandle(Restaurant.MakeFood);

		return phone;
	}
}

接下来的四个类是程序。它们都有 Main 过程。如果您将它们放在同一个项目中,则必须在运行之前指定启动程序。
我们将从直接使用 Invoke 方法调用委托的程序开始。这是一个同步调用。

public class Sync
{
	/// <summary>
	/// Invoking directly
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start time:
		long start = DateTime.Now.Ticks;

		//initialize host
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//clean the place:
		host.CleanUpPlace();

		//set up furniture
		host.SetupFurniture();

		//call food chef into your apartment to prepare food
		//the chef will come and will make the food at your place
		string getFood = restaurantPhone.Invoke("sushi");

		//register when food is done:
		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//prepare yourself:
		host.TakeBathAndDressUp();

		//mark end time:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

图 1 显示了这种情况。

SyncCall.gif

当您使用同步调用时,您指示编译器在主执行线程中执行委托的函数。我会将其与邀请餐厅厨师到我的公寓准备食物联系起来。这暗示了顺序场景。Main 程序中的每个函数调用都发生在前一个调用完成后。当程序调用 MakeFood 函数时,它直到当前函数返回后才能调用下一个函数。最后,程序计算总时间。在这种情况下,它是 11 秒。

第二种情况是异步调用。
程序调用 BeginInvoke 方法,而不是调用委托的 Invoke 方法。
调用之间存在差异。

IAsyncResult asyncResult = restaurantPhone.BeginInvoke("sushi", null, null); 

正如您所见,BeginInvoke 方法的返回值是一个 IasyncResult 对象。我将其与订购食物后收到的收据进行比较。您将使用此收据稍后识别您的订单,以便获取它。
此方法还要求在委托的签名指定的参数之外,额外提供两个参数。
我们现在不关注这两个参数,但稍后我们会讨论它们。在此期间,我们可以用 null 代替它们。
所以,我们先调用 BeginInvoke 并获得收据,然后调用 EndInvoke,将收据传递给调用,并获得结果(根据委托的签名返回的值)。

public class AsyncNoCallBackBadTiming
{
	/// <summary>
	/// Invoking Async No Call Back Bad Timing
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		//call the restaurant to order food and get a token 
		//from them identifying 
		//your order. they immediately start preparing food:
		IAsyncResult asyncResult = restaurantPhone.BeginInvoke
					("sushi", null, null);

		string getFood;

		//call the restaurant, provide the token, get the food, 
		//don't be surprised that
		//you have to wait until the food is ready.
		getFood = restaurantPhone.EndInvoke(asyncResult);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

图 2 显示了这种情况。

AsyncDelegateCallBad.gif

当您使用异步调用时,您指示编译器从线程池中获取一个可用的 Thread (或创建一个新的 thread)。我没有在我的公寓里准备食物,而是使用餐厅的设施来做饭,然后再送给我。
这有一个很大的优点。函数已被调用,其执行转到其他地方。主线程不需要等待函数返回。它可以继续调用下一个函数。

不幸的是,我误判了情况,并在调用 BeginInvoke 后立即调用了 EndInvoke
主线程一直等待直到 EndInvoke 返回。为了让 EndInvoke 函数返回,它必须等到 MakeFood 函数返回。这会阻塞主线程,并且使用异步调用的所有优势都消失了。
总时间与之前相同,都是 11 秒。

在第三种情况中,我们纠正了这个逻辑错误。
现在,先订购食物。在食物准备期间,我可以做一些其他事情,比如打扫公寓,布置家具。
因此,调用了 BeginInvoke ,主线程立即恢复执行流程,调用了其他函数。当调用 EndInvoke 时,食物已经准备好了。
现在总执行时间是 7 秒。
我们节省了 4 秒!!!

public class AsyncNoCallBackGoodTiming
{
    /// <summary>
	/// Invoking Async No Call Back Good Timing
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//call the restaurant to order food and get a token from them 
		//identifying your order. they immediately start preparing food:
		IAsyncResult asyncResult = restaurantPhone.BeginInvoke
					("sushi", null, null);

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		string getFood;

		//call the restaurant, provide the token, get the food
		getFood = restaurantPhone.EndInvoke(asyncResult);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}
}

图 3 解释了这种情况。

AsyncDelegateCallGood.gif

最后一种情况解释了如何使用回调函数。
正如我之前提到的,BeginInvoke 函数接收两个额外的委托签名参数。
第一个参数是 CallBackFunction 委托。
我使用了一个 static 函数。

private static void FoodDeliveryNotification(IAsyncResult result){} 

看看它的签名。它返回 void 并接受 IAsyncResult 参数。
因为这个参数是委托,所以我使用了 new 关键字来创建一个委托的 new 实例,并将其指向 FoodDeliveryNotification 函数。

restaurantPhone.BeginInvoke("sushi", 
	new AsyncCallback(FoodDeliveryNotification), restaurantPhone); 

第二个参数是一个对象,我可以传递任何我想要的对象。在这里,我传递了调用委托本身。正如我们已经知道的,MakeFood 的执行是在另一个线程上进行的。
当函数返回时,它会触发回调函数 FoodDeliveryNotification。函数内会发生几件事情。

  1. 它获取调用委托。
  2. 它调用调用委托的 EndInvoke 函数,传递 IasyncResult
  3. 它处理返回值。

在这种情况下,我的 main 程序不会直接调用 EndInvoke。它通过 CallBack 函数巧妙地完成了。

public class AsyncCallBack
{
        /// <summary>
	/// Invoking Async Call Back
	/// </summary>
	/// <param name="args">
	static void Main(string[] args)
	{
		//mark start:
		long start = DateTime.Now.Ticks;

		//initialize host:
		PartyHost host = new PartyHost("Ed");

		//find a restaurant phone number in phone book;
		//it is your delegate:
		OrderHandle restaurantPhone = host.GetRestaurantPhoneNumber();

		//call the restaurant to order food and get a token 
		//from them identifying your order. 
		//They immediately start preparing food:
		restaurantPhone.BeginInvoke("sushi", new AsyncCallback
			(FoodDeliveryNotification), restaurantPhone);

		//clean up:
		host.CleanUpPlace();

		//set furniture:
		host.SetupFurniture();

		//take care of yourself:
		host.TakeBathAndDressUp();

		//mark the end:
		long end = DateTime.Now.Ticks;

		//register total time:
		Console.WriteLine("Total time {0} seconds", (end - start) / 10000000);

		Console.Read();
	}

	private static void FoodDeliveryNotification(IAsyncResult result)
	{
		//get the delegate:
		OrderHandle handle = (OrderHandle)result.AsyncState;

		//call end invoke:
		string getFood = handle.EndInvoke(result);

		Console.WriteLine(getFood + " at " + DateTime.Now.ToLongTimeString());
	}
}
}

摘要

在这篇文章中,您学习了如何异步调用委托。即使您知道如何去做,也不必一定这样做。通常,当委托调用一个耗时且可能延迟您的程序的 IO 函数时,这样做是有意义的。但请根据您的判断在主程序中放置调用。如果放置不当,它将延迟您的程序,并且不会获得任何好处。

待续...

© . All rights reserved.