很多时候我们需要做一个工作,在一个方法体里面,读取大数据绑定到UI界面,由于长时间的读取,读取独占了线程域,导致界面一直处于假死状态。例如,当应用程序开始读取Web资源时,读取的时效是由网络链路的速度决定的,那么在读取的过程中整个程序都必然处于一种等待状态,这不是我们想要看到的。那么我们有没有一种机制既能解决效率问题同时可以提供代码的可用性呢?有人可能会说,我们可以使用线程池。线程真的是万能的吗?当处理大并发数据量时就能说明这个问题,线程池最大的并发量有限制,而且线程是极度占用资源。
.NET
4.5下提供了一种异步方式,能够有效的避免效率瓶颈并增强应用程序的可维护性,这种机制类似于Node.js原理,这是一个完全单线程的方法,当有数据传递过程时,它首先把数据交给异步处理,再完成后再通过回调返回数据集,我们可以理解为c#版的闭包。它的最大作用是把原来的等待换成异步方式,把UI解放出来,因为UI的活动都是共用一个域的。
先看看传统模式下的无阻塞刷新,首先是XAML页面文件
<Grid> <Grid.RowDefinitions> <RowDefinition Height="*"></RowDefinition> <RowDefinition Height="30"></RowDefinition> </Grid.RowDefinitions> <ListView Grid.Row="0" Width="auto" Height="auto" Name="lvData" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"> <ListView.View> <GridView> <GridViewColumn Header="编号" DisplayMemberBinding="{Binding Path=Id}" Width="100" /> <GridViewColumn Header="随机数" DisplayMemberBinding="{Binding Path=Code}" Width="160"/> </GridView> </ListView.View> </ListView> </Grid>
再新建一个ViewModel类,这个类的作用是为我们的MVVM模式提供一个数据源
public class RandomViewModel { public RandomViewModel() { Model = new ObservableCollection<RandomData>(); } public ObservableCollection<RandomData> Model; public List<RandomData> dataList; public void FillData() { Model.Clear(); for (int i = 0; i < dataList.Count; i++) { Model.Add(dataList[i]); } } public void GetData() { dataList = new List<RandomData>(); Random random = new Random(); for (int i = 0; i < 20000; i++) { dataList.Add(new RandomData() { Id = i, Code = random.NextDouble() }); } } } public class RandomData { public int Id { get; set; } public double Code { get; set; } }
传统模式下的无刷新调用,先在线程内部计算出需要绑定的数据,然后再使用Invoke填充Model.
Thread thread = new Thread(p => { while (true) { ViewModel.GetData(); this.Dispatcher.Invoke(new Action<List<RandomData>>(result => { ViewModel.FillData(); }), ViewModel.dataList); Thread.Sleep(4000); } }); thread.IsBackground = true; thread.Start();
在代码中嵌入线程函数,代码是很分散的,同时也消耗大量资源。为了让我们代码尽可能的做到内外同步执行。我们使用async、await、Task
private async Task AsyncAccess() { while (true) { var getDataListTask = new Task(() => { Thread.Sleep(5000); ViewModel.GetData(); }); getDataListTask.Start(); await getDataListTask; var fillModelTask = Task.Factory.StartNew(() => { ViewModel.FillData(); }, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); await fillModelTask; } }
理论上说,Task.Factory.StartNew也是在线程池上执行,可表示异步操作而且更加通用,其中在Task前加上await使用成为异步函数,不然UI界面仍会有卡顿的现象。
最后一个Invoke方法,需要通过SynchronizationContext.SetSynchronizationContext方法在多线程环境下设置SynchronizationContext.Current属性,将线程控制域转交至UI,设置当前SynchronizationContext相关的TaskScheduler,利用这个TaskScheduler,Task将通过当前SynchronizationContext来执行,实际上上述代码在WPF下等效于调用Dispatcher.BeginInvoke方法。
.net 4.5下已默认封装的相关类库有
访问Web | HttpClient , SyndicationClient |
使用文件 | StorageFile, StreamWriter, StreamReader,XmlReader |
使用图像 | MediaCapture, BitmapEncoder, BitmapDecoder |
WCF程序开发 | Synchronous and Asynchronous Operations |
使用sockets | Socket |