C#委托异步编程

委托异步编程

原文参考:《.Net 指南》

功能概述

在C#中,委托有两种调用方式:Invoke同步调用;BeginInvoke异步调用。Invoke同步是在当前线程,直接调用委托方法,BeginInvoke则通过线程池中的线程实现异步调用。

常用的异步调用有四种方式:
1. 使用EndInvoke对调用线程进行阻止,直到异步方法调用完成
2. 使用IAsyncResult.AsyncWaitHandle进行调用线程阻止,直到WaitHandle收到信号,然后调用EndInvoke
3. 对BeginInvoke返回的IAsyncResult进行轮询,直到IsComplete属性为True,然后调用EndInvoke
4. 向BeginInvoke传递回调函数,异步方法完成之后,将在异步线程上调用该回调函数,应该在回调函数内调用EndInvoke

!!! 需要注意的是,四种方法都需要调用EndInvoke方法

在介绍四种方法之前,首先做一些准备工作,定义相关测试方法和委托

using System;
using System.Threading;

public class AsyncDemo
{
    // 需要进行异步调用的方法
    public string TestMethod(int callDuration)
    {
        //输出当前线程的Id
        Console.WriteLine("Test method begins.Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        //模拟一个耗时操作
        Thread.Sleep(callDuration);

        Console.WriteLine("Test method complete,Thread Id:{0}",Thread.CurrentThread.ManagedThreadId);

        return String.Format("My call time was {0}.", callDuration.ToString());
    }
}

//定义一个异步委托
public delegate string AsyncMethodCaller(int callDuration);

EndInvoke等待异步调用

在调用BeginInvoke之后,可以随时调用EndInvoke,该方法将阻止调用线程,直到异步方法调用完成,EndInvoke方法同时返回异步方法的返回值,需要注意的是:EndInvoke会阻止调用线程,所以不应该在主线程进行调用!

using System;
using System.Collections.Generic;
using System.Threading;

internal class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Main method begins,Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        AsyncDemo ad = new AsyncDemo();

        //创建委托,并将AsyncDemo.TestMethod方法绑定到委托
        AsyncMethodCaller asyncCaller = new AsyncMethodCaller(ad.TestMethod);

        //开始异步调用
        //第一个参数:委托传递的实参
        //第二个参数:回调函数
        //第三个参数:向IAsyncResult.AsyncState传递的参数
        IAsyncResult result = asyncCaller.BeginInvoke(3000, null, null);

        //模拟主线程一些工作
        Thread.Sleep(1000);

        //调用EndInvoke函数,此函数将阻塞当前线程,直到异步方法调用完成,并返回异步方法的返回值
        //在实际应用过程中,EndInvoke不应该在主线程进行调用,避免影响UI的响应
        string testRtn = asyncCaller.EndInvoke(result);

        Console.WriteLine("Async end invoke,return value :{0}",testRtn);

        Console.Read();
    }
}

输出结果:

Main method begins,Thread ID:1
Test method begins.Thread ID:4
Test method complete,Thread Id:4
Async end invoke,return value :My call time was 3000.

使用WaitHandle等待异步调用

BeginInvoke返回IAsyncResult接口对象中包含WaitHandle类型AsyncWaitHandle成员属性,当异步调用完成之后,WaitHandle信号会被设置,可以通过调用WaitHandle.WaitOne来等待调用完成。

注意:在使用完WaitHandle之后,记得显示调用WaitHandle.Close来释放句柄。

using System;
using System.Collections.Generic;
using System.Threading;

internal class Program
{

    public static void Main(string[] args)
    {
        Console.WriteLine("Main method begins,Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        AsyncDemo ad = new AsyncDemo();

        //创建委托,并将AsyncDemo.TestMethod方法绑定到委托
        AsyncMethodCaller asyncCaller = new AsyncMethodCaller(ad.TestMethod);

        //开始异步调用
        //第一个参数:委托传递的实参
        //第二个参数:回调函数
        //第三个参数:向IAsyncResult.AsyncState传递的参数
        IAsyncResult result = asyncCaller.BeginInvoke(3000, null, null);

        //模拟主线程一些工作
        Thread.Sleep(1000);

        Console.WriteLine("Main thread work finish");

        //阻塞当前线程,等待异步调用完成
        result.AsyncWaitHandle.WaitOne();

        //检测异步试试完成了
        Console.WriteLine("Do after wait one,IsAyncComplete:{0}", result.IsCompleted);

        //在调用EndInvoke获取返回值
        string testRtn = asyncCaller.EndInvoke(result);

        Console.WriteLine("Async end invoke,return value :{0}", testRtn);

        Console.Read();
    }
}

输出结果:

Main method begins,Thread ID:1
Test method begins.Thread ID:4
Main thread work finish
Test method complete,Thread Id:4
Do after wait one,IsAyncComplete:True
Async end invoke,return value :My call time was 3000.

轮询IAsyncResult.IsCompleted

在调用线程中,可以通过轮询IAsyncResult.IsCompleted检测异步调用是否完成。

using System;
using System.Collections.Generic;
using System.Threading;

internal class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Main method begins,Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        AsyncDemo ad = new AsyncDemo();

        //创建委托,并将AsyncDemo.TestMethod方法绑定到委托
        AsyncMethodCaller asyncCaller = new AsyncMethodCaller(ad.TestMethod);

        //开始异步调用
        //第一个参数:委托传递的实参
        //第二个参数:回调函数
        //第三个参数:向IAsyncResult.AsyncState传递的参数
        IAsyncResult result = asyncCaller.BeginInvoke(3000, null, null);

        //模拟主线程一些工作
        Thread.Sleep(1000);

        //轮询IAsyncResult.IsCompelted
        //true表示异步调用已经完成
        while (!result.IsCompleted)
        {
            Thread.Sleep(100);
            Console.Write(">");
        }

        //调用EndInvoke函数
        string testRtn = asyncCaller.EndInvoke(result);

        Console.WriteLine("Async end invoke,return value :{0}",testRtn);

        Console.Read();
    }
}

输出结果:

Main method begins,Thread ID:1
Test method begins.Thread ID:4
>>>>>>>>>>>>>>>>>>>Test method complete,Thread Id:4
>Async end invoke,return value :My call time was 3000.

异步回调

在调用BeginInvoke时,可以设置回调方法(签名:void AsyncCallback(IAsyncResult res),该方法在异步方法调用完成时,在异步线程进行调用;同时可以BeginInvoke第三个参数,向回调传递信息。

using System;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.Threading;

internal class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Main method begins,Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        AsyncDemo ad = new AsyncDemo();

        //创建委托,并将AsyncDemo.TestMethod方法绑定到委托
        AsyncMethodCaller asyncCaller = new AsyncMethodCaller(ad.TestMethod);

        //开始异步调用
        //第一个参数:委托传递的实参
        //第二个参数:回调函数
        //第三个参数:向IAsyncResult.AsyncState传递的参数
        IAsyncResult result = asyncCaller.BeginInvoke(3000, TestCallback,
            "Info from thread:" + Thread.CurrentThread.ManagedThreadId);

        //模拟主线程一些工作
        Thread.Sleep(1000);

        Console.WriteLine("Main thread work complete!");

        Console.Read();
    }

    private static void TestCallback(IAsyncResult res)
    {
        //通过IAsyncResult.AsyncState获取BeginInvoke传递的参数
        string infoFromCaller = (string) res.AsyncState;

        Console.WriteLine("Test callback begin,current thread id:{0},state info:{1}", Thread.CurrentThread.ManagedThreadId,
            infoFromCaller);

        AsyncResult result = res as AsyncResult;
        AsyncMethodCaller caller = (AsyncMethodCaller) result.AsyncDelegate;
        string testRtn = caller.EndInvoke(res);

        Console.WriteLine("Async end invoke,return value :{0}", testRtn);
    }
}

输出结果:

Main method begins,Thread ID:1
Test method begins.Thread ID:4
Main thread work complete!
Test method complete,Thread Id:4
Test callback begin,current thread id:4,state info:Info from thread:1 //回调与异步方法同一线程
Async end invoke,return value :My call time was 3000.

关于EndInvoke

根据《.Net指南》要求,在调用BeginInvoke之后,必须调用EndInvoke,其中也没有指出原因,但是根据网上结论,主要是以下三方面:

1. 可能造成内存泄漏(没有任何证据能够证实)
2. 不能捕获异步线程抛出的异常
3. 如果需要获取异步方法的返回参数,必须调用EndInvoke

using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;


public class AsyncDemo
{
    // 需要进行异步调用的方法
    public string TestMethod(int callDuration)
    {
        //输出当前线程的Id
        Console.WriteLine("Test method begins.Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        //模拟一个耗时操作
        Thread.Sleep(callDuration);

        throw new Exception("Excep in async thread......");
    }
}

//定义一个异步委托
public delegate string AsyncMethodCaller(int callDuration);


internal class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("Main method begins,Thread ID:{0}", Thread.CurrentThread.ManagedThreadId);

        AsyncDemo ad = new AsyncDemo();

        //创建委托,并将AsyncDemo.TestMethod方法绑定到委托
        AsyncMethodCaller asyncCaller = new AsyncMethodCaller(ad.TestMethod);

        //开始异步调用
        //第一个参数:委托传递的实参
        //第二个参数:回调函数
        //第三个参数:向IAsyncResult.AsyncState传递的参数
        IAsyncResult result = asyncCaller.BeginInvoke(3000, TestCallback,
            "Info from thread:" + Thread.CurrentThread.ManagedThreadId);

        //模拟主线程一些工作
        Thread.Sleep(1000);

        Console.WriteLine("Main thread work complete!");

        Console.Read();
    }

    private static void TestCallback(IAsyncResult res)
    {
        //通过IAsyncResult.AsyncState获取BeginInvoke传递的参数
        string infoFromCaller = (string) res.AsyncState;

        Console.WriteLine("Test callback complete,current thread id:{0},state info:{1}",
            Thread.CurrentThread.ManagedThreadId,
            infoFromCaller);
    }
}

输出结果:

Main method begins,Thread ID:1
Test method begins.Thread ID:4
Main thread work complete!
Test callback complete,current thread id:4,state info:Info from thread:1

正确的实现方式是,在回调函数中,调用EndInvoke方法,并将使用try…catch进行包裹,修改回调函数如下:

    private static void TestCallback(IAsyncResult res)
    {
        //通过IAsyncResult.AsyncState获取BeginInvoke传递的参数
        string infoFromCaller = (string) res.AsyncState;

        Console.WriteLine("Test callback complete,current thread id:{0},state info:{1}",
            Thread.CurrentThread.ManagedThreadId,
            infoFromCaller);

        //在调用EndInvoke时,应该使用try..catch进行包裹
        try
        {
            AsyncResult result = res as AsyncResult;
            AsyncMethodCaller caller = (AsyncMethodCaller) result.AsyncDelegate;
            caller.EndInvoke(res);
        }
        catch (Exception e)
        {
            Console.WriteLine(e);
        }
    }

猜你喜欢

转载自blog.csdn.net/salvare/article/details/79997856