Hprose for Java客户端(三)
程序员文章站
2022-05-11 19:33:12
...
异步调用
下面我们来开始另一个重要的话题,那就是异步调用。
异步调用相对于同步调用来说确实要难以掌握一些,但是在很多情况下我们却很需要它。那究竟什么时候我们需要使用异步调用呢?
很多时候我们并不确定在进行远程调用时是否能够立即得到返回结果,因为可能由于带宽问题或者服务器本身需要对此调用进行长时间计算而不能马上返回结果给客户端。这种情况下,如果使用同步远程调用,客户端执行该调用的线程将被阻塞,并且在主线程中执行同步远程调用会造成用户界面冻结,这是用户无法忍受的。这时,我们就需要使用异步调用。
虽然您也可以使用多线程加同步调用来完成异步调用(实际上Hprose的异步调用也是如此实现的),但您不必这样做。您可以直接使用Hprose提供的异步调用方式,这将更加简单。
通过invoke方法进行异步调用
通过invoke方式进行异步调用跟同步调用差不多,唯一的区别就是异步调用多了一个回调方法参数。
因为java本身不能向C/C++那样传递函数或方法指针,也不能向C#那样传递方法委托,所以在java中invoke的回调方法是以接口HproseCallback来表示的,它只有一个方法:
其中,第一个参数表示返回结果,第二个参数表示调用参数。如果您不是在进行引用参数传递的调用,那么第二个参数的参数值,跟您调用时的参数值是一致的。
当您通过invoke所调用的方法执行完毕时,您所指定的回调方法将会被调用,其中的参将会自动被传入。
关于通过invoke进行异步调用,我想不用举太多例子,下面这个简单的例子就可以很好的说明如何来使用了:
这个例子展示了同时进行ex1_getId和ex2_getId这两个方法的异步调用。
这里都是通过创建匿名类来实现回调方法的,您也可以用命名类来定义,但通常用匿名类来定义回调方法会更灵活,更直观。
您可能还注意到,我们在程序的最后加上了System.in.read(),因为这里是异步调用,如果不加这一句,调用还没有执行完,可能程序就已经退出了,这样您就看不到任何执行结果了。不过一般情况下,都是在图形用户界面的程序中才会使用异步调用,所以,在那种情况下,您可能需要用其它方法来保证异步调用被完整执行。
这个程序的执行结果为:
但也可能为:
因为调用是异步的,服务器端不一定首先返回哪个结果,所以结果打印的顺序也是不定的。
通过代理接口进行异步调用
除了可以通过invoke方式外,您也可以通过接口方式来进行异步调用,这里我们来举一个稍微复杂点的例子,来说明引用参数传递,容器类型和自定义类型传输,以及如何在调用中指定要返回的结果类型。
这个程序比较长,我们分段来分析。
首先来看接口定义,异步调用的接口方法中,都含有一个HproseCallback参数,该参数在所有的方法参数之后定义,在它之后的还有两个可选参数,一个表示返回值类型,另一个表示是否进行引用参数传递。这两个参数可以同时出现,同时出现时,返回值类型应该在引用参数传递参数之前。
接口中您可以有两种方式来定义返回值类型。
一种是以方法签名方式来直接表示返回值类型,不过这种方式千万不要与同步调用方式混淆,实际上在异步调用过程中不可能通过方法的返回值来直接得到调用的结果。如果返回值声明为原生数据类型,则永远返回该类型所对应的零值(例如int返回0,boolean返回false),如果为引用类型,则永远返回null。而具体的结果是在回调方法中通过第一个参数返回的。
通过方法签名方式来标记返回值是您可以认为是静态方式,因为它在调用过程中不能变化。另一种方法就是通过在HproseCallback参数之后声明返回值类型,这种是动态方式,它可以在调用过程中来指定具体的返回值类型。
第一个调用,我们采用的是静态方式,直接将返回值类型声明为Map<String,String>。
第二个调用返回值类型声明为void类型,并且没有在后面跟随返回类型参数,它表示将按照默认反序列化类型返回数据。
后面两个调用的是同一个方法,该方法返回值声明为void类型,但是后面有跟随返回类型参数。
前一个带入的返回类型为null,这表明将按照默认反序列化类型返回数据,因为这个方法返回的是元素为自定义类型(User)的列表类型,所以它所对应的默认反序列化类型是元素为自定义类型(User)的ArrayList,当然在回调方法中我们就可以通过List<User>泛型接口类型来存取它了。
而后一个调用带入的返回类型为User[],所以,在回调方法中我们可以通过User[]类型来存取它。
关于引用参数传递,我们可以看前两个调用,第一个没有设置引用参数传递参数,它是值传递,第二个设置了引用参数传递,则它的参数在调用后会被改变。
您可能还发现我们在前两个调用的回调方法中作了同步,原因是这两个方法本身也是异步执行的,所以如果不作同步处理,则它们的结果可能会交替输出,这样我们就不容易分辨究竟是不是引用参数传递啦。
后面两个调用没有作同步处理,它们的结果就可能会交替输出,甚至会在前两个方法中间输出内容。
下面是该程序运行的一种可能的结果:
关于异步调用就这么多内容,您只要理清了思路,掌握并灵活运用它并不难。下面我们再来看一下如何在客户端处理远程调用中发生的异常吧。
下面我们来开始另一个重要的话题,那就是异步调用。
异步调用相对于同步调用来说确实要难以掌握一些,但是在很多情况下我们却很需要它。那究竟什么时候我们需要使用异步调用呢?
很多时候我们并不确定在进行远程调用时是否能够立即得到返回结果,因为可能由于带宽问题或者服务器本身需要对此调用进行长时间计算而不能马上返回结果给客户端。这种情况下,如果使用同步远程调用,客户端执行该调用的线程将被阻塞,并且在主线程中执行同步远程调用会造成用户界面冻结,这是用户无法忍受的。这时,我们就需要使用异步调用。
虽然您也可以使用多线程加同步调用来完成异步调用(实际上Hprose的异步调用也是如此实现的),但您不必这样做。您可以直接使用Hprose提供的异步调用方式,这将更加简单。
通过invoke方法进行异步调用
通过invoke方式进行异步调用跟同步调用差不多,唯一的区别就是异步调用多了一个回调方法参数。
因为java本身不能向C/C++那样传递函数或方法指针,也不能向C#那样传递方法委托,所以在java中invoke的回调方法是以接口HproseCallback来表示的,它只有一个方法:
void handler(Object result, Object[] arguments);
其中,第一个参数表示返回结果,第二个参数表示调用参数。如果您不是在进行引用参数传递的调用,那么第二个参数的参数值,跟您调用时的参数值是一致的。
当您通过invoke所调用的方法执行完毕时,您所指定的回调方法将会被调用,其中的参将会自动被传入。
关于通过invoke进行异步调用,我想不用举太多例子,下面这个简单的例子就可以很好的说明如何来使用了:
package hprose.exam; import hprose.client.HproseHttpClient; import hprose.common.HproseCallback; import java.io.IOException; public class ClientExam9 { public static void main(String[] args) throws IOException { HproseHttpClient client = new HproseHttpClient(); client.useService("http://localhost:8084/HproseExamServer/Methods"); client.invoke("ex1_getId", new HproseCallback() { public void handler(Object result, Object[] args) { System.out.println(result); } }); client.invoke("ex2_getId", new HproseCallback() { public void handler(Object result, Object[] args) { System.out.println(result); } }); System.in.read(); } }
这个例子展示了同时进行ex1_getId和ex2_getId这两个方法的异步调用。
这里都是通过创建匿名类来实现回调方法的,您也可以用命名类来定义,但通常用匿名类来定义回调方法会更灵活,更直观。
您可能还注意到,我们在程序的最后加上了System.in.read(),因为这里是异步调用,如果不加这一句,调用还没有执行完,可能程序就已经退出了,这样您就看不到任何执行结果了。不过一般情况下,都是在图形用户界面的程序中才会使用异步调用,所以,在那种情况下,您可能需要用其它方法来保证异步调用被完整执行。
这个程序的执行结果为:
引用
Exam1
Exam2
Exam2
但也可能为:
引用
Exam2
Exam1
Exam1
因为调用是异步的,服务器端不一定首先返回哪个结果,所以结果打印的顺序也是不定的。
通过代理接口进行异步调用
除了可以通过invoke方式外,您也可以通过接口方式来进行异步调用,这里我们来举一个稍微复杂点的例子,来说明引用参数传递,容器类型和自定义类型传输,以及如何在调用中指定要返回的结果类型。
package hprose.exam; import hprose.client.HproseHttpClient; import hprose.common.HproseCallback; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; interface ITest { Map<String, String> swapKeyAndValue(Map<String, String> strmap, HproseCallback callback); void swapKeyAndValue(Map<String, String> strmap, HproseCallback callback, boolean byRef); void getUserList(HproseCallback callback, Class returnType); } public class ClientExam10 { public static void main(String[] args) throws IOException { HproseHttpClient client = new HproseHttpClient(); client.useService("http://localhost:8084/HproseExamServer/Methods"); final ITest test = (ITest) client.useService(ITest.class, "ex2"); Map<String, String> map = new HashMap<String, String>(); map.put("January", "Jan"); map.put("February", "Feb"); map.put("March", "Mar"); map.put("April", "Apr"); map.put("May", "May"); map.put("June", "Jun"); map.put("July", "Jul"); map.put("August", "Aug"); map.put("September", "Sep"); map.put("October", "Oct"); map.put("November", "Nov"); map.put("December", "Dec"); test.swapKeyAndValue(map, new HproseCallback() { public void handler(Object result, Object[] args) { synchronized (test) { Map<String, String> map = (Map<String, String>)args[0]; Map<String, String> map2 = (Map<String, String>)result; System.out.println("byVal:"); System.out.println(map); System.out.println(map2); System.out.println(); } } }); test.swapKeyAndValue(map, new HproseCallback() { public void handler(Object result, Object[] args) { synchronized (test) { Map<String, String> map = (Map<String, String>)args[0]; Map<String, String> map2 = (Map<String, String>)result; System.out.println("byRef:"); System.out.println(map); System.out.println(map2); System.out.println(); } } }, true); test.getUserList(new HproseCallback() { public void handler(Object result, Object[] args) { List<User> users = (List<User>)result; for (User user : users) { System.out.printf("name: %s, ", user.getName()); System.out.printf("age: %d, ", user.getAge()); System.out.printf("sex: %s, ", user.getSex()); System.out.printf("birthday: %s, ", user.getBirthday()); System.out.printf("married: %s.", user.isMarried()); System.out.println(); } System.out.println(); } }, null); test.getUserList(new HproseCallback() { public void handler(Object result, Object[] args) { User[] users = (User[])result; for (User user : users) { System.out.printf("name: %s, ", user.getName()); System.out.printf("age: %d, ", user.getAge()); System.out.printf("sex: %s, ", user.getSex()); System.out.printf("birthday: %s, ", user.getBirthday()); System.out.printf("married: %s.", user.isMarried()); System.out.println(); } System.out.println(); } }, User[].class); System.in.read(); } }
这个程序比较长,我们分段来分析。
首先来看接口定义,异步调用的接口方法中,都含有一个HproseCallback参数,该参数在所有的方法参数之后定义,在它之后的还有两个可选参数,一个表示返回值类型,另一个表示是否进行引用参数传递。这两个参数可以同时出现,同时出现时,返回值类型应该在引用参数传递参数之前。
接口中您可以有两种方式来定义返回值类型。
一种是以方法签名方式来直接表示返回值类型,不过这种方式千万不要与同步调用方式混淆,实际上在异步调用过程中不可能通过方法的返回值来直接得到调用的结果。如果返回值声明为原生数据类型,则永远返回该类型所对应的零值(例如int返回0,boolean返回false),如果为引用类型,则永远返回null。而具体的结果是在回调方法中通过第一个参数返回的。
通过方法签名方式来标记返回值是您可以认为是静态方式,因为它在调用过程中不能变化。另一种方法就是通过在HproseCallback参数之后声明返回值类型,这种是动态方式,它可以在调用过程中来指定具体的返回值类型。
第一个调用,我们采用的是静态方式,直接将返回值类型声明为Map<String,String>。
第二个调用返回值类型声明为void类型,并且没有在后面跟随返回类型参数,它表示将按照默认反序列化类型返回数据。
后面两个调用的是同一个方法,该方法返回值声明为void类型,但是后面有跟随返回类型参数。
前一个带入的返回类型为null,这表明将按照默认反序列化类型返回数据,因为这个方法返回的是元素为自定义类型(User)的列表类型,所以它所对应的默认反序列化类型是元素为自定义类型(User)的ArrayList,当然在回调方法中我们就可以通过List<User>泛型接口类型来存取它了。
而后一个调用带入的返回类型为User[],所以,在回调方法中我们可以通过User[]类型来存取它。
关于引用参数传递,我们可以看前两个调用,第一个没有设置引用参数传递参数,它是值传递,第二个设置了引用参数传递,则它的参数在调用后会被改变。
您可能还发现我们在前两个调用的回调方法中作了同步,原因是这两个方法本身也是异步执行的,所以如果不作同步处理,则它们的结果可能会交替输出,这样我们就不容易分辨究竟是不是引用参数传递啦。
后面两个调用没有作同步处理,它们的结果就可能会交替输出,甚至会在前两个方法中间输出内容。
下面是该程序运行的一种可能的结果:
引用
name: Amy, age: 26, sex: Female, birthday: 1983-12-03, married: true.
name: Bob, age: 20, sex: Male, birthday: 1989-06-12, married: false.
name: Chris, age: 29, sex: Unknown, birthday: 1980-03-08, married: true.
name: Alex, age: 17, sex: InterSex, birthday: 1992-06-14, married: false.
byVal:
name: Amy, age: 26, sex: Female, birthday: 1983-12-03, married: true.
name: Bob, age: 20, sex: Male, birthday: 1989-06-12, married: false.
name: Chris, age: 29, sex: Unknown, birthday: 1980-03-08, married: true.
name: Alex, age: 17, sex: InterSex, birthday: 1992-06-14, married: false.
{October=Oct, January=Jan, April=Apr, February=Feb, August=Aug, June=Jun, November=Nov, July=Jul, May=May, December=Dec, March=Mar, September=Sep}
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
byRef:
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
name: Bob, age: 20, sex: Male, birthday: 1989-06-12, married: false.
name: Chris, age: 29, sex: Unknown, birthday: 1980-03-08, married: true.
name: Alex, age: 17, sex: InterSex, birthday: 1992-06-14, married: false.
byVal:
name: Amy, age: 26, sex: Female, birthday: 1983-12-03, married: true.
name: Bob, age: 20, sex: Male, birthday: 1989-06-12, married: false.
name: Chris, age: 29, sex: Unknown, birthday: 1980-03-08, married: true.
name: Alex, age: 17, sex: InterSex, birthday: 1992-06-14, married: false.
{October=Oct, January=Jan, April=Apr, February=Feb, August=Aug, June=Jun, November=Nov, July=Jul, May=May, December=Dec, March=Mar, September=Sep}
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
byRef:
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
{Sep=September, Feb=February, Mar=March, Apr=April, Oct=October, Jan=January, May=May, Nov=November, Dec=December, Jul=July, Aug=August, Jun=June}
关于异步调用就这么多内容,您只要理清了思路,掌握并灵活运用它并不难。下面我们再来看一下如何在客户端处理远程调用中发生的异常吧。