使用异步方式调用同步方法(实例详解)
说明:
.net compact framework 中不支持异步委托调用,也就是 begininvoke 和 endinvoke 方法。
begininvoke 方法启动异步调用。该方法与您需要异步执行的方法具有相同的参数,还有另外两个可选参数。第一个参数是一个 asynccallback 委托,该委托引用在异步调用完成时要调用的方法。第二个参数是一个用户定义的对象,该对象将信息传入回调方法。begininvoke 会立即返回,而不等待异步调用完成。begininvoke 返回一个可用于监视异步调用进度的 iasyncresult。
endinvoke 方法检索异步调用的结果。在调用 begininvoke 之后随时可以调用该方法。如果异步调用尚未完成,则 endinvoke 会一直阻止调用线程,直到异步调用完成。endinvoke 的参数包括您需要异步执行的方法的 out 和 ref 参数(在 visual basic 中为 <out> byref 和 byref)以及由 begininvoke 返回的 iasyncresult。
说明:
visual studio 2005 中的 intellisense 功能显示 begininvoke 和 endinvoke 的参数。如果您没有使用 visual studio 或类似工具,或您使用的是带有 visual studio 2005 的 c#,请参见 异步编程概述 以获取为这些方法定义的参数的说明。
本主题中的代码示例演示了使用 begininvoke 和 endinvoke 进行异步调用的四种常用方法。调用 begininvoke 之后,您可以执行下列操作:
进行某些操作,然后调用 endinvoke 一直阻止到调用完成。
使用 iasyncresult.asyncwaithandle 属性获取 waithandle,使用其 waitone 方法一直阻止执行直到发出 waithandle 信号,然后调用 endinvoke。
轮询由 begininvoke 返回的 iasyncresult,以确定异步调用何时完成,然后调用 endinvoke。
将用于回调方法的委托传递给 begininvoke。异步调用完成后,将在 threadpool 线程上执行该方法。回调方法调用 endinvoke。
重要说明:
无论您使用何种方法,都要调用 endinvoke 来完成异步调用。
定义测试方法和异步委托
下面的代码示例演示异步调用同一个长时间运行的方法 testmethod 的各种方式。testmethod 方法会显示一条控制台消息,说明该方法已开始处理,休眠了几秒钟,然后结束。testmethod 有一个 out 参数,该参数用于演示此种参数添加到 begininvoke 和 endinvoke 的签名中的方式。您可以按同样的方式处理 ref 参数。
下面的代码示例演示 testmethod 的定义和名为 asyncmethodcaller 的、可用来异步调用 testmethod 的委托。若要编译代码示例,必须包括 testmethod 的定义和 asyncmethodcaller 委托。
c#
using system;
using system.threading;
namespace asynctest
{
public class asyncdemo
{
//方法一
public string testmethod(int callduration, out int threadid)
{
console.writeline("test method begins.");
thread.sleep(callduration);
threadid = thread.currentthread.managedthreadid;
return string.format("my call time was {0}.", callduration.tostring());
}
//方法二
public float addxy(float x, float y)
{
return x * y;
}
}
//定义两个对应于方法的委托,参数同方法一致,委托的声明写在调用类里也是一样的,不限
public delegate string asyncmethodcaller(int callduration, out int threadid);
public delegate float asyncmethodcaller2(float x, float y);
}
使用 endinvoke 等待异步调用
异步执行方法的最简单方式是通过调用委托的 begininvoke 方法来开始执行方法,在主线程上执行一些操作,然后调用委托的 endinvoke 方法。endinvoke 可能会阻止调用线程,因为该方法直到异步调用完成后才返回。这种方式非常适合执行文件或网络操作。
重要说明:
因为 endinvoke 可能会阻塞,所以不应从服务于用户界面的线程调用该方法。
c#
using system;
using system.threading;
namespace asynctest
{
public class asyncmain
{
public static void main()
{
// the asynchronous method puts the thread id here.
int threadid;
//实例化.
asyncdemo ad = new asyncdemo();
// 定义委托
asyncmethodcaller caller = new asyncmethodcaller(ad.testmethod);
//通过委托调用方法,回调函数为null
iasyncresult result = caller.begininvoke(3000,
out threadid, null, null);
thread.sleep(0);
console.writeline("main thread {0} does some work.",
thread.currentthread.managedthreadid);
// call endinvoke to wait for the asynchronous call to complete,会阻塞
// and to retrieve the results.
string returnvalue = caller.endinvoke(out threadid, result);
console.writeline("the call executed on thread {0}, with return value \"{1}\".",
threadid, returnvalue);
}
}
}
使用 waithandle 等待异步调用
您可以使用由 begininvoke 返回的 iasyncresult 的 asyncwaithandle 属性来获取 waithandle。异步调用完成时会发出 waithandle 信号,而您可以通过调用 waitone 方法等待它。
如果您使用 waithandle,则在异步调用完成之前或之后,但在通过调用 endinvoke 检索结果之前,还可以执行其他处理。
说明:
调用 endinvoke 时不会自动关闭等待句柄。如果释放对等待句柄的所有引用,则当垃圾回收功能回收等待句柄时,将释放系统资源。若要在等待句柄使用完毕后立即释放系统资源,请调用 waithandle.close 方法来释放等待句柄。显式释放可释放的对象时,垃圾回收的工作效率会更高。
c#
using system;
using system.threading;
namespace asynctest
{
public class asyncmain
{
static void main()
{
// the asynchronous method puts the thread id here.
int threadid;
//实例化
asyncdemo ad = new asyncdemo();
//定义委托.
asyncmethodcaller caller = new asyncmethodcaller(ad.testmethod);
//调用
iasyncresult result = caller.begininvoke(3000,
out threadid, null, null);
thread.sleep(0);
console.writeline("main thread {0} does some work.",
thread.currentthread.managedthreadid);
// wait for the waithandle to become signaled.
result.asyncwaithandle.waitone();
// perform additional processing here.
// call endinvoke to retrieve the results.
string returnvalue = caller.endinvoke(out threadid, result);
// close the wait handle.
result.asyncwaithandle.close();
console.writeline("the call executed on thread {0}, with return value \"{1}\".",
threadid, returnvalue);
}
}
}
/*输出如下:
main thread 1 does some work.
test method begins.
the call executed on thread 3, with return value "my call time was 3000.".
*/
轮询异步调用完成
您可以使用由 begininvoke 返回的 iasyncresult 的 iscompleted 属性来发现异步调用何时完成。从用户界面的服务线程中进行异步调用时可以执行此操作。轮询完成允许调用线程在异步调用在 threadpool 线程上执行时继续执行。
c#c++vb
using system;
using system.threading;
namespace asynctest
{
public class asyncmain
{
static void main() {
// the asynchronous method puts the thread id here.
int threadid;
//实例化.
asyncdemo ad = new asyncdemo();
//声明委托
asyncmethodcaller caller = new asyncmethodcaller(ad.testmethod);
//调用.
iasyncresult result = caller.begininvoke(3000,
out threadid, null, null);
//轮询.
while(result.iscompleted == false) {
thread.sleep(250);
console.write(".");
}
//取结果.
string returnvalue = caller.endinvoke(out threadid, result);
console.writeline("\nthe call executed on thread {0}, with return value \"{1}\".",
threadid, returnvalue);
}
}
}
/* 输出:
test method begins.
.............
the call executed on thread 3, with return value "my call time was 3000.".
*/
异步调用完成时执行回调方法
如果启动异步调用的线程不需要是处理结果的线程,则可以在调用完成时执行回调方法。回调方法在 threadpool 线程上执行。
若要使用回调方法,必须将表示回调方法的 asynccallback 委托传递给 begininvoke。也可以传递包含回调方法要使用的信息的对象。在回调方法中,可以将 iasyncresult(回调方法的唯一参数)强制转换为 asyncresult 对象。然后,可以使用 asyncresult.asyncdelegate 属性获取已用于启动调用的委托,以便可以调用 endinvoke。
有关示例的说明:
testmethod 的 threadid 参数为 out 参数(在 visual basic 中为 <out> byref),因此 testmethod 从不使用该参数的输入值。会将一个虚拟变量传递给 begininvoke 调用。如果 threadid 参数为 ref 参数(在 visual basic 中为 byref),则该变量必须为类级别字段,这样才能同时传递给 begininvoke 和 endinvoke。
传递给 begininvoke 的状态信息是一个格式字符串,回调方法使用该字符串来设置输出消息的格式。因为作为类型 object 进行传递,所以状态信息必须强制转换为正确的类型才能被使用。
回调是在 threadpool 线程上进行的。threadpool 线程是后台线程,这些线程不会在主线程结束后保持应用程序的运行,因此示例的主线程必须休眠足够长的时间以便回调完成。
c#
using system;
using system.threading;
using system.runtime.remoting.messaging;
namespace asynctest
{
public class asyncmain
{
static void main()
{
//实例化类.
asyncdemo ad = new asyncdemo();
//实例两个不同的委托,分别对应不同的方法.
asyncmethodcaller caller = new asyncmethodcaller(ad.testmethod);
asyncmethodcaller2 caller2 = new asyncmethodcaller2(ad.addxy);
// the threadid parameter of testmethod is an out parameter, so
// its input value is never used by testmethod. therefore, a dummy
// variable can be passed to the begininvoke call. if the threadid
// parameter were a ref parameter, it would have to be a class-
// level field so that it could be passed to both begininvoke and
// endinvoke.
int dummy = 0;
// 调用时指定回调方法,在异步执行完后会自动调用该方法,用来取得执行的结果
iasyncresult result = caller.begininvoke(3000,
out dummy,
new asynccallback(callbackmethod),
"the call executed on thread {0}, with return value \"{1}\".");
caller2.begininvoke(2, 3, callbackmethod2, "this is add");
console.writeline("the main thread {0} continues to execute...",
thread.currentthread.managedthreadid);
thread.sleep(4000);
console.writeline("the main thread ends.");
}
// the callback method must have the same signature as the
// asynccallback delegate.
private void callbackmethod(iasyncresult ar)
{
// retrieve the delegate.
asyncresult result = (asyncresult) ar;
//强制转换成对应的委托对象
asyncmethodcaller caller = (asyncmethodcaller) result.asyncdelegate;
// retrieve the format string that was passed as state
// information.
string formatstring = (string) ar.asyncstate;
// define a variable to receive the value of the out parameter.
// if the parameter were ref rather than out then it would have to
// be a class-level field so it could also be passed to begininvoke.
int threadid = 0;
// call endinvoke to retrieve the results.
string returnvalue = caller.endinvoke(out threadid, ar);
// use the format string to format the output message.
console.writeline(formatstring, threadid, returnvalue);
}
private void callbackmethod2(iasyncresult ar)
{
// retrieve the delegate.
asyncresult result = (asyncresult)ar;
//强制转换成对应的委托对象
asyncmethodcaller2 caller = (asyncmethodcaller2)result.asyncdelegate;
// 取得状态
string formatstring = (string)ar.asyncstate;
//调用异步完成的方法,取得执行结果
string returnvalue = caller.endinvoke(ar).tostring();
//此种方式是在windows应用程序给控件赋值时用,因为在非创建线程不能操作控件
this.invoke(new action<string>((u) => tb_datainfo.text += u), returnvalue);
//tb_datainfo.text = caller.endinvoke(out threadid, ar);
// use the format string to format the output message.
//console.writeline(formatstring, threadid, returnvalue);
}
}
}
}