C#异步调用获取结果方法:主要有三种,也可以说是四种(官方说四种,电子书说三种),官方在MSDN上已经有详细的说明: 链接
需要了解到获取异步执行的返回值,意味着你需要调用Delegate的BeginInvoke方法,而不是Invoke方法。
第一种就是书上没有说的,但是官方还是给出来的,就是通过调用EndInvoke方法来获取内容,查看如下代码:
class MyState
{
public int ThreadId = 0;
public int Data = 0;
public MyState()
{
}
}
class AsyncInvoke
{
private MyState State = null;
public AsyncInvoke()
{
State = new MyState();
}
private int TakesAWhile(int data, int ms)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(ms);
Console.WriteLine("TakesAWhile completed");
return ++data;
}
public delegate int TakesAWhileDelegate(int data, int ms);
public void RunEndInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
}
TakesAWhile started
TakesAWhile completed
Result: 6
通过上面可以知道使用EndInvoke阻塞了主线程(RunEndInvoke函数),同时需要使用BeginInvoke的返回值ar作为EndInvoke的入参。
第二种方法Polling(轮询)BeginInvoke的返回值(IAsyncResult中的属性IsCompleted),将RunEndInvoke替换成下面的函数RunPolling:
public void RunPolling()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
while (ar.IsCompleted == false)
{
Thread.Sleep(500);
}
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
结果同上,其实看到这里你也发现第二种跟第一种没有很大区别,所以书上没有阐明第一种方法,其实如果只是为了获取结果,用第一种方法就足够了。其实这两个例子没有很好的体现BeginInvoke的异步的优势,好像跟使用Invoke方法没有很大区别,都是事先了阻塞主线程,如下所示:
public void RunInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
int result = dl.Invoke(5, 3000);
Console.WriteLine(string.Format("Result: {0}", result));
}
public void RunEndInvoke()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
// Do others in main thread.
Console.WriteLine("Do others in main thread...");
//
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
这样输出结果如下所示:
Do others in main thread...
TakesAWhile started
TakesAWhile completed
Result: 6
这样你可以放别的工作在BeginInvoke的之后,在主线程执行,等到执行了,可以使用阻塞主线程来等待异步执行的结果来进行下一步的需要,这样情况能够部分提高工作效率,通常用于执行相互独立的模块,然后再等待两边结果(主线程以及异步线程的结果)来进行下一步的操作。
好了,讲第三种的方法来获取异步结果了,就是Wait Handle。 handle是通过BeginInvoke的返回值(ar)中的一个属性AsyncWaitHandle来获得的。一开始AsyncWaitHandle是没有初始化的,但是只要引用该属性,操作系统实现它,同时调用其waitone函数去等待异步执行完的信号。如果使用没有带参的waitone函数,则无限期阻塞主线程来等待异步执行,一旦执行完,则返回, 如下代码所示:
public void RunWaitHandle()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, null, null);
ar.AsyncWaitHandle.WaitOne();
Console.WriteLine("Wait for 3 seconds.");
int result = 0;
result = dl.EndInvoke(ar);
ar.AsyncWaitHandle.Close();
Console.WriteLine(string.Format("Result: {0}", result));
}
如果使用带参的,如waitone(1000, false)来判断当前返回值是true则意味着等待1秒,第二参数来判断是否退出同步。
从上面三种方法来看,的确是挺好的,但是每次都需要主线程去等待,大哥不好当,所以就有了第四种方法,就是使用BegingInvoke的第三个参数,该函数是在BeginInvoke调用的函数执行完后自动执行,不然也不叫做callback。
第四个参数通常将异步函数的delegate传递过去,这样在callback函数中就可以方便的获取到异步函数的执行结果或者返回值。
public void AsyncCompleted(IAsyncResult ar)
{
if (ar != null)
{
Console.WriteLine("After the async operation.");
TakesAWhileDelegate dl = ar.AsyncState as TakesAWhileDelegate;
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}
}
public void RunAsyncCallback()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, AsyncCompleted, dl);
Console.WriteLine("Finish RunAsyncCallback.");
}
public void RunAsyncCallback2()
{
TakesAWhileDelegate dl = new TakesAWhileDelegate(TakesAWhile);
dl.BeginInvoke(5, 3000, ar => {
Console.Write("After async operation.");
int result = 0;
result = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Result: {0}", result));
}, null);
Console.WriteLine("Finish RunAsyncCallback.");
}
private MyState TakesAWhile(int data)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(3000);
Console.WriteLine("TakesAWhile completed");
return new MyState(){Data=++data, ThreadId=Thread.CurrentThread.ManagedThreadId};
}
private delegate MyState TaskAWhileDelegate2(int data);
public void RunEndInvokeWithStructReturn()
{
TaskAWhileDelegate2 dl = new TaskAWhileDelegate2(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, null, null);
MyState state = null;
state = dl.EndInvoke(ar);
Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", state.Data, state.ThreadId));
}
输出结果为:
private void TakesAWhile(int data, int ms, ref MyState state)
{
Console.WriteLine("TakesAWhile started");
Thread.Sleep(ms);
Console.WriteLine("TakesAWhile completed");
state.Data = ++data;
state.ThreadId = Thread.CurrentThread.ManagedThreadId;
}
private delegate void TakesAWhileDelegate3(int data, int ms, ref MyState state);
public void RunEndInvokeWithRefStruct()
{
TakesAWhileDelegate3 dl = new TakesAWhileDelegate3(TakesAWhile);
IAsyncResult ar = dl.BeginInvoke(5, 3000, ref State, null, null);
dl.EndInvoke(ref State, ar); // this State could be any other reference to MyState
Console.WriteLine(string.Format("Data: {0}, ThreadId: {1}", State.Data, State.ThreadId));
}
上面讲述的主要是Thread或者task的异步调用BeginInvoke的结果获取方法,但是如果该异步函数中涉及到输出参数,就如刚才说的复杂对象中的成员是controller的话,就又有一章来讲了,对于UI以及后台线程的交互这是一个重要点,如果你不想你的UI经常假死的话,请收看下一回:Controller中BeginInvoke与Invoke的区别以及应用。
AD的优势是异步函数的强类型保证,参数的输入都是规定好的(delegate声明),如果需要传递参数可以在函数中使用ref关键字(只针对引用类型,值类型无效果)。由于异步调用时使用task底层实现的,所以他具有了task的优势,方便使用,但是也有其缺点,不能够想thread那样提供丰富的对线程设置(后台,优先级,不能中断)。