这次是委托的完结篇。
委托的隐式异步调用
异步
先说说委托的委托的隐式异步调用,也顺便为后面的协程线程迭代器啥的开个头。
首先我们先讲一下啥叫异步呢,中文的异步和英文的异步有所不同。中文的同步表示的是“两边同时发生”。相反,英文的异步表示的是:
“二者同时独立进行,分支线程不互相等待并发生资源的争抢”
这和我们理解的中文“同步”概念是类似的。即中文中的异步等于英文中的同步。中文中的同步等于英文中的异步。
那么C#的同步和异步有哪些不同呢 :
- 每个运行的程序都是一个进程Process,该进程中有主线程thread和其他多个线程。
- 异步调用的底层机理是多线程。同步调用则都在同一个线程内。
- 串行==同步==单线程、并行==异步==多线程。
我们在委托中,使用BeginInvoke函数来实现委托的隐式异步调用。
我们来写个例子:
首先我们先引入using System.Threading。
然后我们定义委托,来调用一个方法,方法里会自动倒数五个数
public delegate void AsynDelegate();
class Asyn
{
public void AsynFunc()
{
for (int i = 0; i < 5; i++)
{
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine("委托函数中有值" + i);
Thread.Sleep(1000);
}
}
}
这个函数会自动倒数五个数。然后我们主函数里这样写,我们的代码主线程中运行完委托后会倒数十个数,颜色是蓝色的
static void Main(string[] args)
{
Asyn asynInstance = new Asyn();
AsynDelegate asynDelegate = new AsynDelegate(asynInstance.AsynFunc);
asynDelegate.Invoke();
for (int i = 0; i < 10; i++)
{
Console.BackgroundColor = ConsoleColor.Blue;
Console.WriteLine("主线程中有值" + i);
Thread.Sleep(1000);
}
}
然后我们看看结果怎么样:
我们首先Invoke了委托(红色),然后会执行Main函数里的For循环(蓝色),所以红色先执行,蓝色后执行,二者非常分明。
然后我们把委托执行的Invoke函数改为我们的BeginInvoke。
asynDelegate.BeginInvoke(null,null);
然后我们执行程序,有:
我们可以看到,执行了BeginInvoke后二者是同步的,主线程中会进行for循环,委托里也会执行for循环,二者互不干扰。
隐式
这样就实现了隐式异步调用,有人问了,异步调用看出来了,但隐式这个概念体现在哪呢,C#在我们调用BeginInvoke的时候,在后台帮我们开了一个新的线程,这个逻辑是没有在代码中体现出来的,只在我们的控制台结果中体现出来,我们可以通过获得线程ID来看到两边的区别。我们在主函数里这样写:
Console.WriteLine("主线程ID是"+Thread.CurrentThread.ManagedThreadId);
委托里添加两行(一行是改颜色):
Console.BackgroundColor = ConsoleColor.Red;
Console.WriteLine("委托线程ID是" + Thread.CurrentThread.ManagedThreadId);
然后我们可以看到结果:
两个打白点的位置就是我们两个进程的ID,一个是1,一个是3,这两个线程是互相独立的。
有参有返回值异步调用
既然我们有了隐式异步调用,我们要有稍微复杂一点的用法怎么办,比如说我们要有参数列表,要有返回值,要能在异步执行完毕后观察信息。。。。。等等等等。
这个时候我们就需要使用与异步调用相对应的EndInvoke来获得我们BeginInvoke完毕后的参数了。有
以下几点需要注意:
- BeginInvoke返回值是接口IAsyncResult的对象,这个对象里保存者委托的返回值信息。
- BeginInvoke是多线程的,EndInvoke会堵塞调用线程,在执行EndInvoke后才能接着执行,若有逻辑在EndInvoke后,那么会等到BiginInvoke开启的线程执行完毕才能执行这段逻辑。
- EndInvoke的参数即为IAsyncResult的对象。
- EndInvoke的返回值即为委托包装的函数的返回值,类型即为返回值类型。
我们用一个例子来表明,首先我们对刚才的委托和委托包装的函数做一点点修改,让它有返回值有参数,参数为我们要循环的次数,返回值为我们的次数+1:
public int AsynFunc(int count)
{
for (int i = 0; i < count; i++)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("委托函数中有值" + i);
Thread.Sleep(1000);
}
return count+1;
}
然后我们在主函数里这样写:
public delegate int AsynDelegate(int i);
class Program
{
static void Main(string[] args)
{
Asyn asynInstance = new Asyn();
Console.WriteLine("输入你想要的遍历次数");
int count = int.Parse(Console.ReadLine());
AsynDelegate asynDelegate = new AsynDelegate(asynInstance.AsynFunc);
IAsyncResult result = asynDelegate.BeginInvoke(count, null, null);
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("主线程中有值" + i);
Thread.Sleep(1000);
}
int a = asynDelegate.EndInvoke(result);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("异步委托返回的结果是" + a);
}
}
循环次数改为自己指定,然后我们BeginInvoke的参数要加上我们的委托函数实参,而且它的返回值是我们的IAsyncResult的对象。然后使用EndInvoke获得返回值。结果是这样的:
我们获得了异步的返回结果,按照我们的逻辑,返回结果是输入的值+1。如果我们将EndInvoke和那个for循环掉个,那么调用线程会等待这个委托线程执行完后才会接着执行,这现象叫做阻塞。即为:
。。。。。。
IAsyncResult result = asynDelegate.BeginInvoke(count, null, null);
int a = asynDelegate.EndInvoke(result);
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine("主线程中有值" + i);
Thread.Sleep(1000);
}
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("异步委托返回的结果是" + a);
那么结果是:
这就是EndInvoke的用法,逻辑上会阻塞调用线程,但是如果我们想在委托执行完毕后进行一定的逻辑,而且确保异步、不会干扰调用线程呢。那么我们需要使用回调方法。
异步的回调方法
我们刚刚调用BeginInvoke的时候,会发现,后面两个的默认的参数都是被置空的:
这里的参数为
- 第一个是回调方法的委托,它是AsyncCallback委托的实例,它包含的函数参数必须为IAsyncResult接口的对象。
- 第二个表示回调函数的参数,为Object类型,该值被存在IAsyncResult接口的对象的AsyncState中。
这里的回调委托就是我们的异步调用结束后会执行的函数,它被包装在AsyncCallback中,并且参数必须为IAsyncResult接口的对象:
我们可以在这个线程可以识别到的地方来定义它:
public void AsynResultFunc(IAsyncResult ar)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("异步委托执行完啦!");
}
然后我们在主函数里把委托修改一下:
AsyncCallback callback = new AsyncCallback(AsynResultFunc);
asynDelegate.BeginInvoke(count, callback, null);
然后我们就可以监控到异步执行完毕了:
然后我们就可以在这个函数中进行一些操作,例如说获得调用函数的委托实例和获得返回值等:
public void AsynResultFunc(IAsyncResult ar)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("异步委托执行完啦!");
//获得委托对象:
System.Runtime.Remoting.Messaging.AsyncResult asyncResult = ar as System.Runtime.Remoting.Messaging.AsyncResult;
AsynDelegate asynDelegate = asyncResult.AsyncDelegate as AsynDelegate;
//获得函数的返回值
int a = asynDelegate.EndInvoke(ar);
Console.WriteLine("异步委托包装的方法返回值为" + a);
}
由于传入的为Object类型,所以我们要进行类型转换。
这样我们的输出结果为:
太好了!它完美的执行了我们的逻辑,而且不会阻塞我们的调用线程(棒读)!