跨线程操作GUI
来源:互联网 发布:创建用户的sql语句 编辑:程序博客网 时间:2024/05/19 11:50
跨线程操作GUI(转)
无论是WIN32还是Windows Form还是WPF还是Swing,都不支持GUI线程之外的线程直接访问其API。今天我们来回顾一下这个发展过程。一个普通的操作是怎么被封装封装再封装的。
Win32
在Windows SDK时代,我们都知道,界面就是一个大的WndProc控制的。
switch (message){case WM_PAINT:case WM_DESTROY:default:return DefWindowProc(hWnd, message, wParam, lParam);}
如果我们需要另外一个线程去做一些耗时的IO操作,同时要回调回来更新的界面,这个时候要么自己定义一个回调队列,然后在WM_IDLE的时候处理,要么就利用窗口本身的消息队列,自己创建一个消息类型:
// at startupint OUR_APP_IO_COMPLETED = RegisterWindowMessage("OUR_APP_IO_COMPLETED");----// after io completedPostMessage(hwnd, OUR_APP_IO_COMPLETED, xxx, xxx);
这样,我们就可以让界面更新的操作在GUI线程完成了。
.NET 1.0 Windows Forms
Windows Forms的出现使得很多东西简单多了。跨线程的GUI操作也简化了许多。因为我们有了Delegate和Control.Invoke。前者就是一个函数指针的升级版,Control.Invoke就是PostMessage的升级版:
public delegate void IOCompletedHandler();public void OnIOCompleted() {control.Invoke(new IOCompletedHandler(HandleIOCompleted));}public void HandleIOCompleted() {}
在内部Control维护了一个threadCallbackList。调用Invoke相当于
1、把Delegate和参数封装到一起,压入到threadCallbackList中
2、确保ThreadCallbackMessage被注册了(RegisterWindowMessage)
3、PostMessage,触发Callback
4、WaitForHandle,等待GUI线程完成处理(如果是BeginInvoke则可以省略这个步骤)
而Windows Forms的WndProc的消息循环,就需要加一个case语句,来响应PostMessage。在得到消息的同时,去threadCallbackList中取出需要回调的Delegate,然后一一调用。
Control.Invoke确实是让我们的生活方便很多。但是它还是不能避免所谓的BeginInvoke之舞(BeginInvoke Dance):
//http://ikriv.com:8765/en/prog/info/dotnet/MysteriousHang.htmldelegate void MyHandlerDelegate(); void MyHandler(){// "The BeginInvoke dance" if (this.InvokeRequired) // assuming this descends from Control {BeginInvoke( new MyHandlerDelegate(MyHandler) );return;}// assume we are on the main GUI thread do GUI stuff }
InvokeRequired内部实现就是线程安全地比较一下当前线程和GUI线程的线程ID是不是相等的。
另外Control.Invoke还需要我们确保对应的Control的句柄已经创建。为了避免这个问题,WinForm有一个MarshingControl可以给我们使用。
Application.ThreadContext.FromCurrent().MarshingControl;
.NET 2.0 Windows Forms
如果我提供了一个回调接口。我一般会直接调用回调方法,如果我知道回调方法必须在GUI线程中执行的话,用必须用Control.Invoke来调 用回调方法。所以,在缺乏信息的情况下,回调接口的提供者是没有办法保证回调方法的执行线程的环境的。所以,按照Strategy模式,我们可以定义一个 CallbackStrategy
public interface CallbackExecutor {void Execute(Delegate callback, object[] args);}public class DefaultCallbackExecutor : CallbackExecutor {public void Execute(Delegate callback, object[] args) {callback.Invoke(args);}}public class WindowsFormsCallbackExecutor : CallbackExecutor {private Control marshalingControl;public WindowsFormsCallbackExecutor() {Application.ThreadContext context = Application.ThreadContext.FromCurrent();if (context != null){this.marshalingControl= context.MarshalingControl;}}public void Execute(Delegate callback, object[] args) {marshalingControl.Invoke(callback, args);}}
然后回调方法的提供者就不用为难了,它只要调用CallbackExecutor就可以了,同时回调方法也可以安心地直接去干自己的事情。从而,谁都不需要做BeginInvoke Dance了。
这就是.NET引入的SynchronizationContext的基本原理。由于微软不是接口爱好者,所以它省去了共同的那个接口,直接让 WindowsFormSynchronizationContext继承自SynchronizationContext类了。 SynchronizationContext是线程唯一的,取得当前线程的Sync Context可以使用:
SynchronizationContext.Current.Post(delegate{//your code}, null);
与SynchronizationContext类同时引入的还有AsyncOperationManager,AsyncOperation。它 又是SynchronizationContext的封装。一般来说,推荐使用AsyncOperationManager。 AsyncOperationManager在创建AsyncOperation的时候会取得当前线程的Sync Context,并存储在SyncOperation之中。 所以后来利用AsyncOperation来回调的时候,就会用一开始创建时候所在线程的context来回调。
// initialize in GUI threadasyncOperation = AsyncOperationManager.Create(null);// when you want to call backasyncOperation.Post(delegate{//your code}, null);
像BackgroundWorker这样的类,之所以其Callback可以直接访问GUI,其秘诀就在于回调不是直接调用的,而是有 AsyncOperation来间接代劳的。只要AsyncOperation是在GUI线程创建的,所有由它代劳的回调都会经过 MarshalingControl来做Control.Invoke。
.NET 3.0 WPF
Control.Invoke变成了Dispatcher.Invoke。内部实现还是一样的,有一个Callback队列,然后PostMessage。同时引入的还有DispatcherSynchronizationContext。
Conculsion
从直接PostMessage到Control.Invoke到SynchronizationContext到 AsyncOperationManager到BackgroundWorker,职责最终推卸到了回调的提供方。但是由于引入的时间不一致,很多回调提 供方是没有使用AsyncOperation的。这个时候,我们自己需要自己去包装,比如:
Code System;using System.ComponentModel;using System.Net;using log4net;namespace xxx{public class WebClientFileUploader : FileUploaderListeners, FileUploader{private static readonly ILog LOGGER = LogManager.GetLogger(typeof (WebClientFileUploader));private readonly WebClient webClient;private readonly Uri address;private readonly AsyncOperation asyncOperation;public WebClientFileUploader(Uri address){asyncOperation = AsyncOperationManager.CreateOperation(null);webClient = new WebClient();webClient.UploadFileCompleted += HandleUploadFileCompleted;this.address = address;}private void HandleUploadFileCompleted(object sender, UploadFileCompletedEventArgs eventArgs){try{var result = eventArgs.Result;LOGGER.Info("Request completed.");asyncOperation.Post(state => HandleCompleted(result), null);}catch (Exception exception){LOGGER.Error("Request failed.", exception);asyncOperation.Post(state => HandleError(exception), null);}}public void Cancel(){LOGGER.Info("Cancelling the request.");webClient.CancelAsync();}public void Upload(string fileName){LOGGER.Info("Starting to upload to " + address + " with file " + fileName + " using real web client.");webClient.UploadFileAsync(address, WebRequestMethods.Http.Post, fileName);}}}
这样,我们就不需要在手工地去用Control.Invoke了,也不应该再直接用Control.Invoke了。
- 跨线程操作GUI
- WPF跨线程操作GUI控件
- [初试Android]Service+跨线程操作GUI的一个小例子
- GUI线程
- Swing GUI线程安全问题
- 跨线程操作UI
- 跨线程操作窗体
- 常用GUI操作
- GUI--触摸屏操作
- Matlab GUI对话框操作
- Python+PyQt,GUI操作
- matlab 等待gui操作
- JMeter GUI基本操作
- Git Gui可视化操作
- 关于C++中的GUI线程
- 关于GUI线程和worker线程
- 关于GUI线程和worker线程
- 非UI线程的跨线程操作
- NStimer 在滚动ScrollView的时候停止,在新线程中使用NSTimer
- jsp表达式
- edit mask 实现输出特定格式
- sql
- WP7开发中的一些小技巧和问题解决(不定期更新)
- 跨线程操作GUI
- jsp注释
- 加快数据表空间恢复
- Stanford Online Machine Learning 学习笔记1——单变量线性回归
- 大数计算器
- HDU 2094 拓扑
- TQ2440 LED流水灯调试过程
- hibernate cascade属性 all-delete-orphan
- 黑马程序员-JAVA中多线程常用的API