c#winform耗时操作处理-异步 联系客服

发布时间 : 星期三 文章c#winform耗时操作处理-异步更新完毕开始阅读ac34ab06844769eae009edf7

儿,不用干等着这个耗时操作返回。.Net中的这种异步编程模型,就简化了多线程编程,我们甚至都不用去关心Thread类,就可以做一个异步操作出来。

去了还要回

实际上上面演示的耗时操作是“一去不复返”的操作(相当于WCF中的One-Way操作),也就是我发起这个操作后,我就不用管它了,我甚至不关心它运算的结果。但大部分时候我们需要这样的操作:执行完后返回来更新一下UI,比如告诉用户一声我执行完了或者显示执行结果。那这样我们就要考虑异步调用的几种方式了。如果我们要从异步操作里获取结果,我们就得调用EndInvoke方法,那我们又用什么手段来得到异步操作完成的信号呢?因为如果异步操作没有完成,我们就直接调用EndInvoke方法,这样就会阻塞,一直等到异步操作执行完毕后才会执行。

在继续讨论之前我们来看看BeginInvoke的返回值:

1:publicinterface IAsyncResult 2: {

3:object AsyncState { get; } 4:

5: WaitHandle AsyncWaitHandle { get; } 6:

7:bool CompletedSynchronously { get; } 8:

9:bool IsCompleted { get; } 10: }

根据BeginInvoke返回的结果,我们就有两种调用异步操作的方式:

轮询

IAsyncResult的IsCompleted属性会在异步操作结束后返回true,否则返回false。那么我们就可以用一个循环不断的访问IsCompleted属性,当IsCompleted为true的时候再调用EndInvoke方法:

1:publicpartialclass LongTimeForm : Form 2: {

3:public LongTimeForm() 4: {

5: InitializeComponent();

6: Debug.Listeners.Add(new ConsoleTraceListener()); 7: } 8:

9:privateint LongTimeMethod() 10: {

11: Thread.Sleep(50 * 1000); 12:return 10; 13: } 14:

15:privatevoid btnLongTime_Click(object sender, EventArgs e) 16: {

17://咱们这儿就不自己定义新的委托了,.Net为我们定义了一串的通用委托使用 18: Func longTimeAction = new Func(LongTimeMethod);

19: IAsyncResult asynResult = longTimeAction.BeginInvoke(null, null); 20:

21://可以做别的事情

22:while (!asynResult.IsCompleted) 23: { 24:

25: }

26:int result = longTimeAction.EndInvoke(asynResult); 27: 28: }

29:protectedoverridevoid WndProc(ref Message m) 30: {

31: Debug.WriteLine(m.Msg.ToString()); 32:base.WndProc(ref m); 33: } 34: }

WaitOne

在IAsyncResult里还有一个AsyncWaitHandle属性,这是一个WaitHandle类型的属性,这个对象有一个WaitOne方法,还能接受一个超时时间,它会等待这个超时时间指定的长度:

1:privateint LongTimeMethod() 2: {

3: Thread.Sleep(50 * 1000); 4:return 10; 5: }

6:privatevoid btnLongTime_Click(object sender, EventArgs e) 7: {

8: Func longTimeAction = new Func(LongTimeMethod);

9: IAsyncResult asynResult = longTimeAction.BeginInvoke(null, null); 10:

11://可以继续处理别的事情 12:

13:if (asynResult.AsyncWaitHandle.WaitOne(10000, true)) 14: {

15:int result = longTimeAction.EndInvoke(asynResult); 16: } 17: }

上面的代码的意思就是,异步调用耗时操作后,继续干自己的事儿,然后干完自己的事儿再来等着一个信号,啥信号呢?就是这个耗时操作完成的信号。而且您还别让我等得太久,等久了我就不耐烦了(我可只等待10秒钟啊)。晕死,上面这耗时操作就要执行50秒钟,你就等10秒钟,这不是玩我吗(10秒钟时间过去了,这个WaitOne就不再等待了,线程将继续执行)。

回调

其实不管是上面使用轮询的方式还是使用WaitOne等待一个信号量,还是要等待。等待是个让人很恼火的事情。.Net考虑了这一点,为我们准备了回调的方式:你异步调用后继续干你的事儿,等你执行完后,你告我一声就ok了。

1:privatevoid btnLongTime_Click(object sender, EventArgs e) 2: {

3: Func longTimeAction = new Func(LongTimeMethod); 4://这里使用了一个lambda表达式,省了不少力啊

5: IAsyncResult asynResult = longTimeAction.BeginInvoke((result) => { 6:int ret = longTimeAction.EndInvoke(result); 7: }, null); 8: }

当异步操作完成后,上面代码中用lambda表达式表示的一个回调方法就会执行,在这里调用EndInvoke获取耗时操作的结果。在这里想想为什么用lambda,如果不用lambda也不用匿名方法(不管你用啥,实际上就是形成一个闭包)你要怎么做?留作您自己思考。

更新UI

上面四种异步调用的方式:一种无声无息,一去不复返。一种轮询、一种等待,外加一个回调。实际上耗时操作的结果都让代码给“吃”了。一般情况下,我们处理完耗时操作总要有所表现吧,比如更新一下UI等等。那我们就来看看如何更新UI。 当你运行这个程序时,当耗时操作结束后,啪嚓一下,程序出异常了:

啊?为什么啊,为什么就不行啊。还不能从不是创建这个控件的线程中访问这个控件。那怎么办?看来我们的异步操作还得改进改进啊。

从异常开始

在上一篇文章中,为了提高用户体验,使用delegate构造一个异步操作,但是在这个异步操作里操作UI控件的属性的时候却发生异常。实际上使用delegate构造异步操作这种方式,在背后还是创建了一个worker thread,从不是创建UI的thread里去操作UI元素的属性就会抛出这个异常。

不过,如果我们不在Visual Studio里运行这个程序,直接运行,这个异常却不会出现。通过查看异常的StackTrace,发现该异常是在获取Control的句柄时抛出的: at System.Windows.Forms.Control.get_Handle()

at System.Windows.Forms.Control.set_WindowText(String value) at System.Windows.Forms.Control.set_Text(String value) at System.Windows.Forms.ButtonBase.set_Text(String value) //..省略...

祭出Reflector,看看相关代码:

1:public IntPtr Handle 2: { 3: get 4: {

5:if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) &&this.InvokeRequired) 6: {

7:thrownew InvalidOperationException(SR.GetString(\, newobject[] { this.Name })); 8: }