代码改变世界

探讨一种Silverlight的异步编程模式

2010-03-27 16:14  xiaosonl  阅读(1025)  评论(3编辑  收藏  举报

在开发RIA程序时,处理异步操作是个挺麻烦的问题,尤其是串行的异步调用,经常代码会写的嵌套好几层。之前老赵也介绍过一种方法来简化异步操作。我这是另一种思路。

在之前的Silverlight程序开发过程中,我的编程模式是这样的:

  1. 用户执行一个UI操作,并引发UI事件,此时在UI线程开始执行后台代码。
  2. 在代码执行过程中,遇到需要异步的操作,如网络请求等,就在另一个线程执行该操作,执行完毕后利用事件或委托继续执行接下来的操作。如果这时异步操作比较多,就会有很多的事件或委托跳来跳去的,很不好看。

这时我们可以换一种思路,反过来做,每一次的操作,我们都发起一个新的线程来执行,不再从UI线程发起。执行过程中遇到的异步操作(网格请求,或者UI操作,因为这时UI有关的操作都必须使用BeginInvoke来调用),可以用ResetEvent来阻塞住发起操作的线程,异步操作执行结束后再继续 因为一开始是在非UI线程中发起操作,所以不会阻塞UI。

总结一下:

  1. 每个操作都是发起一个新的线程来执行。
  2. 每个异步方法都可以用ResetEvent来模拟成同步方法,就可以很方便像同步方法那样调用和组织, 但在简单的异步场景中优势不明显。
  3. 缺点是影响性能,用性能来换代码的可读性和可维护性。

最后附上一个简易示例代码(未经运行测试和优化),演示将[下载数据 => 处理数据并更新UI] 的过程模拟成同步操作:

 

代码
    public partial class MainPage : UserControl
    {
        
public MainPage()
        {
            InitializeComponent();
        }

        
private void btnCommand_Click(object sender, RoutedEventArgs e)
        {
            Command();
        }

        
//执行操作
        public void Command()
       {
            ThreadPool.QueueUserWorkItem(t 
=>
            {
                UIInvoke(() 
=>
                { 
                    
//弹出一个ProgressBar,提示操作正在进行中
                  });

                
//下载数据
                string downloadData = DownLoad();
                
//更新UI
                UIInvoke(() => txtMessage.Text = downloadData);

                UIInvoke(() 
=>
                {
                    
//关闭ProgressBar,提示操作结束
                });
            });
        }

        
//以同步的方式调用UI线程执行一个操作
        private void UIInvoke(Action action)
       {
            Dispatcher dispatcher 
= Deployment.Current.Dispatcher;

            
if (dispatcher.CheckAccess())
            {
                action();
            }
            
else
            {
                ManualResetEvent resetEvent 
= new ManualResetEvent(false);
                resetEvent.Reset();
                dispatcher.BeginInvoke(() 
=>
                {
                    action();
                    resetEvent.Set();
                });
                resetEvent.WaitOne();
            }
        }

        
private string DownLoad()
        {
            ManualResetEvent reset 
= new ManualResetEvent(false);
            reset.Reset();
            
//下载数据
            WebClient client = new WebClient();
            
string result = "";
            client.DownloadStringCompleted 
+= (s, e) =>
            {
                result 
= e.Result;
                reset.Set();
            };
            client.DownloadStringAsync(
new Uri("http://下载地址"));
            reset.WaitOne();

            
return result;
        }
    }