C# 学习笔记:委托(6):异步

这次是委托的完结篇。

委托的隐式异步调用

异步

先说说委托的委托的隐式异步调用,也顺便为后面的协程线程迭代器啥的开个头。

首先我们先讲一下啥叫异步呢,中文的异步和英文的异步有所不同。中文的同步表示的是“两边同时发生”。相反,英文的异步表示的是:

“二者同时独立进行,分支线程不互相等待并发生资源的争抢”

这和我们理解的中文“同步”概念是类似的。即中文中的异步等于英文中的同步。中文中的同步等于英文中的异步

那么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完毕后的参数了。有

以下几点需要注意:

  1. BeginInvoke返回值是接口IAsyncResult的对象,这个对象里保存者委托的返回值信息。
  2. BeginInvoke是多线程的,EndInvoke会堵塞调用线程,在执行EndInvoke后才能接着执行,若有逻辑在EndInvoke后,那么会等到BiginInvoke开启的线程执行完毕才能执行这段逻辑。
  3. EndInvoke的参数即为IAsyncResult的对象。
  4. 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类型,所以我们要进行类型转换。

这样我们的输出结果为:

太好了!它完美的执行了我们的逻辑,而且不会阻塞我们的调用线程(棒读)!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值