C# 中的委托 -尝试深入了解 -第二部分
如果需要,
引言
这是我打算深入探讨该主题的系列文章的第二部分。如果您没有阅读第一部分,我强烈建议您阅读。在此处 阅读文章。
在第二部分中,我想讨论调用委托所表示的函数的不同方法。主要部分实际上是何时以及如何异步调用委托。使用异步调用被认为是高级技术,因为它涉及线程。我希望在您阅读完本文后,您将完全准备好在应用程序中使用这些调用。我想采用一种不同的方法来解释委托调用。我将尝试虚构一个生活情景并将其应用于编程环境。我认为这将更容易理解这个问题,并意识到使用委托并没有什么神秘之处;一切都有其目标和目的。
生活中的问题
今晚我要招待一些朋友。我有一份在我朋友来之前需要做的事情清单。我希望有条理,这是我创建的清单。
- 打扫房间
- 做一些食物
- 摆桌子
- 准备好(洗澡、穿衣等)
好吧,我不认为这些事情很难做,但我不是厨师。所以我需要一些专业帮助。
我可以打电话订餐。我订餐的餐厅有一些很棒的政策。如果您愿意,他们可以在您的公寓里准备食物。他们会送餐并使用您的厨房做饭。您实际上可以看到正在发生的事情。另一种选择是他们在餐厅提前做好,然后送餐。
程序模型
现在我们尝试将这种情况放入编程中。
首先,我将创建一个名为 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 显示了这种情况。
当您使用同步调用时,您指示编译器在主执行线程中执行委托的函数。我会将其与邀请餐厅厨师到我的公寓准备食物联系起来。这暗示了顺序场景。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 显示了这种情况。

当您使用异步调用时,您指示编译器从线程池中获取一个可用的 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 解释了这种情况。

最后一种情况解释了如何使用回调函数。
正如我之前提到的,BeginInvoke
函数接收两个额外的委托签名参数。
第一个参数是 CallBackFunction
委托。
我使用了一个 static
函数。
private static void FoodDeliveryNotification(IAsyncResult result){}
看看它的签名。它返回 void
并接受 IAsyncResult
参数。
因为这个参数是委托,所以我使用了 new
关键字来创建一个委托的 new
实例,并将其指向 FoodDeliveryNotification
函数。
restaurantPhone.BeginInvoke("sushi",
new AsyncCallback(FoodDeliveryNotification), restaurantPhone);
第二个参数是一个对象,我可以传递任何我想要的对象。在这里,我传递了调用委托本身。正如我们已经知道的,MakeFood
的执行是在另一个线程上进行的。
当函数返回时,它会触发回调函数 FoodDeliveryNotification
。函数内会发生几件事情。
- 它获取调用委托。
- 它调用调用委托的
EndInvoke
函数,传递IasyncResult
。 - 它处理返回值。
在这种情况下,我的 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 函数时,这样做是有意义的。但请根据您的判断在主程序中放置调用。如果放置不当,它将延迟您的程序,并且不会获得任何好处。
待续...