重新想象 Windows 8 Store Apps (66) - 后台任务: 下载和上传
重新想象 Windows 8 Store Apps (66) - 后台任务: 下载和上传
作者:webabcd
介绍
重新想象 Windows 8 Store Apps 之 后台任务
- 后台下载任务
- 后台上传任务
示例
扩展了 DownloadOperation 和 UploadOperation,以便下载进度或上传进度可通知
BackgroundTask/TransferModel.cs
/* * 扩展了 DownloadOperation 和 UploadOperation,以便下载进度或上传进度可通知 */ using System; using System.ComponentModel; using Windows.Networking.BackgroundTransfer; namespace XamlDemo.BackgroundTask { public class TransferModel : INotifyPropertyChanged { public DownloadOperation DownloadOperation { get; set; } public UploadOperation UploadOperation { get; set; } public string Source { get; set; } public string Destination { get; set; } private string _progress; public string Progress { get { return _progress; } set { _progress = value; RaisePropertyChanged("Progress"); } } public event PropertyChangedEventHandler PropertyChanged; protected void RaisePropertyChanged(string name) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(name)); } } } }
1、演示后台下载任务的应用
BackgroundTask/TransferDownload.xaml
<Page x:Class="XamlDemo.BackgroundTask.TransferDownload" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.BackgroundTask" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <ScrollViewer Height="100"> <TextBlock Name="lblMsg" FontSize="14.667" TextWrapping="Wrap" /> </ScrollViewer> <Button Name="btnAddDownload" Content="新增一个下载任务" Margin="0 10 0 0" Click="btnAddDownload_Click" /> <Button Name="btnPause" Content="暂停所有下载任务" Margin="0 10 0 0" Click="btnPause_Click" /> <Button Name="btnResume" Content="继续所有下载任务" Margin="0 10 0 0" Click="btnResume_Click" /> <Button Name="btnCancel" Content="取消所有下载任务" Margin="0 10 0 0" Click="btnCancel_Click" /> <ListView Name="listView" Padding="0 10 0 0" Height="300"> <ListView.ItemTemplate> <DataTemplate> <StackPanel Margin="0 5" Background="Blue"> <TextBlock Text="{Binding Source}" Margin="5" /> <TextBlock Text="{Binding Destination}" Margin="5" /> <TextBlock Text="{Binding Progress}" Margin="5" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackPanel> </Grid> </Page>
BackgroundTask/TransferDownload.xaml.cs
/* * 演示后台下载任务的应用 * * BackgroundDownloader - 后台下载任务管理器 * CostPolicy - 下载的成本策略,BackgroundTransferCostPolicy 枚举 * Default - 允许在高成本(比如 2G 3G)网络上传输(默认值) * UnrestrictedOnly - 不允许在高成本(比如 2G 3G)网络上传输 * Always - 无论如何均可传输,即使在漫游时 * ServerCredential - 与服务端通信时的凭据 * ProxyCredential - 使用代理时的身份凭据 * SetRequestHeader(string headerName, string headerValue) - 设置 http 请求头 * CreateDownload(Uri uri, IStorageFile resultFile) - 创建一个下载任务,返回 DownloadOperation 对象 * Group - 用于分组下载任务 * static GetCurrentDownloadsAsync(string group) - 获取指定组下的所有下载任务 * static GetCurrentDownloadsAsync() - 获取未与组关联的所有下载任务 * * DownloadOperation - 下载任务对象 * CostPolicy - 下载的成本策略,BackgroundTransferCostPolicy 枚举 * Group - 获取此下载任务的所属组 * Guid - 获取此下载任务的标识 * RequestedUri - 下载的源 URI * ResultFile - 下载的目标文件 * GetResponseInformation() - 下载完成后获取到的服务端响应信息,返回 ResponseInformation 对象 * ActualUri - 下载源的真实 URI * Headers - 服务端响应的 HTTP 头 * StatusCode - 服务端响应的状态码 * Pause() - 暂停此任务 * Resume() - 继续此任务 * StartAsync() - 新增一个下载任务,返回 IAsyncOperationWithProgress<DownloadOperation, DownloadOperation> 对象 * AttachAsync() - 监视已存在的下载任务,返回 IAsyncOperationWithProgress<DownloadOperation, DownloadOperation> 对象 * Progress - 获取下载进度,返回 BackgroundDownloadProgress 对象 * * BackgroundDownloadProgress - 后台下载任务的下载进度对象 * BytesReceived - 已下载的字节数 * TotalBytesToReceive - 总共需要下载的字节数,未知则为 0 * Status - 下载状态,BackgroundTransferStatus 枚举 * Idle, Running, PausedByApplication, PausedCostedNetwork, PausedNoNetwork, Completed, Canceled, Error * HasResponseChanged - 服务端响应了则为 true * HasRestarted - 当下载连接断掉后,系统会通过 http range 头向服务端请求断点续传,如果服务端不支持断点续传则需要重新下载,此种情况则为 true */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; using System.Linq; using Windows.Networking.BackgroundTransfer; using Windows.Storage; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.Web; namespace XamlDemo.BackgroundTask { public sealed partial class TransferDownload : Page { // 下载任务的集合 private ObservableCollection<TransferModel> _transfers = new ObservableCollection<TransferModel>(); // 所有下载任务的关联的 CancellationTokenSource 对象 private CancellationTokenSource _cancelToken = new CancellationTokenSource(); public TransferDownload() { this.InitializeComponent(); Init(); } private async void Init() { listView.ItemsSource = _transfers; // 获取全部下载任务 await LoadDownloadAsync(); } // 加载全部下载任务 private async Task LoadDownloadAsync() { IReadOnlyList<DownloadOperation> downloads = null; try { // 获取所有后台下载任务 downloads = await BackgroundDownloader.GetCurrentDownloadsAsync(); } catch (Exception ex) { lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; return; } if (downloads.Count > 0) { List<Task> tasks = new List<Task>(); foreach (DownloadOperation download in downloads) { // 监视指定的后台下载任务 tasks.Add(HandleDownloadAsync(download, false)); } await Task.WhenAll(tasks); } } // 新增一个下载任务 private async void btnAddDownload_Click(object sender, RoutedEventArgs e) { // 下载地址(注意需要在 Package.appxmanifest 中增加对 .rar 类型文件的关联) Uri sourceUri = new Uri("https://files.cnblogs.com/webabcd/Windows8.rar", UriKind.Absolute); StorageFile destinationFile; try { // 保存的目标地址 destinationFile = await KnownFolders.DocumentsLibrary.CreateFileAsync("Windows8.rar", CreationCollisionOption.GenerateUniqueName); } catch (Exception ex) { lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; return; } // 创建一个后台下载任务 BackgroundDownloader backgroundDownloader = new BackgroundDownloader(); DownloadOperation download = backgroundDownloader.CreateDownload(sourceUri, destinationFile); // 监视指定的后台下载任务 await HandleDownloadAsync(download, true); } /// <summary> /// 监视指定的后台下载任务 /// </summary> /// <param name="download">后台下载任务</param> /// <param name="isNew">是否是新增的任务</param> private async Task HandleDownloadAsync(DownloadOperation download, bool isNew) { try { // 将 DownloadOperation 附加到 TransferModel,以便下载进度可通知 TransferModel transfer = new TransferModel(); transfer.DownloadOperation = download; transfer.Source = download.RequestedUri.ToString(); transfer.Destination = download.ResultFile.Path; transfer.Progress = "0 / 0"; _transfers.Add(transfer); lblMsg.Text += "Task Count: " + _transfers.Count.ToString(); lblMsg.Text += Environment.NewLine; // 当下载进度发生变化时的回调函数 Progress<DownloadOperation> progressCallback = new Progress<DownloadOperation>(DownloadProgress); if (isNew) await download.StartAsync().AsTask(_cancelToken.Token, progressCallback); // 新增一个后台下载任务 else await download.AttachAsync().AsTask(_cancelToken.Token, progressCallback); // 监视已存在的后台下载任务 // 下载完成后获取服务端的响应信息 ResponseInformation response = download.GetResponseInformation(); lblMsg.Text += "Completed: " + response.ActualUri + "-" + response.StatusCode.ToString(); lblMsg.Text += Environment.NewLine; } catch (TaskCanceledException) // 调用 CancellationTokenSource.Cancel() 后会抛出此异常 { lblMsg.Text += "Canceled: " + download.Guid; lblMsg.Text += Environment.NewLine; } catch (Exception ex) { // 将异常转换为 WebErrorStatus 枚举,如果获取到的是 WebErrorStatus.Unknown 则说明此次异常不是涉及 web 的异常 WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult); lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; } finally { _transfers.Remove(_transfers.First(p => p.DownloadOperation == download)); } } // 进度发生变化时,更新 TransferModel 的 Progress,以便通知 private void DownloadProgress(DownloadOperation download) { TransferModel transfer = _transfers.First(p => p.DownloadOperation == download); transfer.Progress = download.Progress.BytesReceived.ToString("#,0") + " / " + download.Progress.TotalBytesToReceive.ToString("#,0"); } // 暂停全部后台下载任务 private void btnPause_Click(object sender, RoutedEventArgs e) { lblMsg.Text += "All Paused"; lblMsg.Text += Environment.NewLine; foreach (TransferModel transfer in _transfers) { if (transfer.DownloadOperation.Progress.Status == BackgroundTransferStatus.Running) { transfer.DownloadOperation.Pause(); } } } // 继续全部后台下载任务 private void btnResume_Click(object sender, RoutedEventArgs e) { lblMsg.Text += "All Resumed"; lblMsg.Text += Environment.NewLine; foreach (TransferModel transfer in _transfers) { if (transfer.DownloadOperation.Progress.Status == BackgroundTransferStatus.PausedByApplication) { transfer.DownloadOperation.Resume(); } } } // 取消全部后台下载任务 private void btnCancel_Click(object sender, RoutedEventArgs e) { _cancelToken.Cancel(); _cancelToken.Dispose(); _cancelToken = new CancellationTokenSource(); } } }
2、演示后台上传任务的应用
BackgroundTask/TransferUpload.xaml
<Page x:Class="XamlDemo.BackgroundTask.TransferUpload" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:XamlDemo.BackgroundTask" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"> <Grid Background="Transparent"> <StackPanel Margin="120 0 0 0"> <ScrollViewer Height="100"> <TextBlock Name="lblMsg" FontSize="14.667" TextWrapping="Wrap" /> </ScrollViewer> <Button Name="btnAddUpload" Content="新增一个上传任务(一次请求上传一个文件)" Margin="0 10 0 0" Click="btnAddUpload_Click" /> <Button Name="btnAddMultiUpload" Content="新增一个上传任务(一次请求上传多个文件)" Margin="0 10 0 0" Click="btnAddMultiUpload_Click" /> <Button Name="btnCancel" Content="取消所有上传任务" Margin="0 10 0 0" Click="btnCancel_Click" /> <ListView Name="listView" Padding="0 10 0 0" Height="300"> <ListView.ItemTemplate> <DataTemplate> <StackPanel Margin="0 5" Background="Blue"> <TextBlock Text="{Binding Source}" Margin="5" /> <TextBlock Text="{Binding Destination}" Margin="5" /> <TextBlock Text="{Binding Progress}" Margin="5" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> </ListView> </StackPanel> </Grid> </Page>
BackgroundTask/TransferUpload.xaml.cs
/* * 演示后台上传任务的应用 * * BackgroundUploader - 后台上传任务管理器 * CostPolicy - 上传的成本策略,BackgroundTransferCostPolicy 枚举 * Default - 不允许在高成本(比如 2G 3G)网络上传输 * UnrestrictedOnly - 允许在高成本(比如 2G 3G)网络上传输 * Always - 无论如何均可传输,即使在漫游时 * ServerCredential - 与服务端通信时的凭据 * ProxyCredential - 使用代理时的身份凭据 * SetRequestHeader(string headerName, string headerValue) - 设置 http 请求头 * CreateUpload(Uri uri, IStorageFile sourceFile) - 创建一个上传任务,返回 UploadOperation 对象 * CreateUploadFromStreamAsync(Uri uri, IInputStream sourceStream) - 以流的方式创建一个上传任务 * CreateUploadAsync(Uri uri, IEnumerable<BackgroundTransferContentPart> parts) - 创建一个包含多个上传文件的上传任务 * Group - 用于分组上传任务 * static GetCurrentUploadsAsync(string group) - 获取指定组下的所有上传任务 * static GetCurrentUploadsAsync() - 获取未与组关联的所有上传任务 * * UploadOperation - 上传任务对象 * CostPolicy - 上传的成本策略,BackgroundTransferCostPolicy 枚举 * Group - 获取此上传任务的所属组 * Guid - 获取此上传任务的标识 * RequestedUri - 上传任务所请求的服务端地址 * SourceFile - 需要上传的文件,如果是一次上传多个文件则此属性为 null * GetResponseInformation() - 上传完成后获取到的服务端响应信息,返回 ResponseInformation 对象 * ActualUri - 上传服务的真实 URI * Headers - 服务端响应的 HTTP 头 * StatusCode - 服务端响应的状态码 * StartAsync() - 新增一个上传任务,返回 IAsyncOperationWithProgress<UploadOperation, UploadOperation> 对象 * AttachAsync() - 监视已存在的上传任务,返回 IAsyncOperationWithProgress<UploadOperation, UploadOperation> 对象 * Progress - 获取上传进度,返回 BackgroundUploadProgress 对象 * * BackgroundUploadProgress - 后台上传任务的上传进度对象 * BytesSent - 已上传的字节数 * TotalBytesToSend - 总共需要上传的字节数 * Status - 上传状态,BackgroundTransferStatus 枚举 * Idle, Running, PausedByApplication, PausedCostedNetwork, PausedNoNetwork, Completed, Canceled, Error * HasResponseChanged - 服务端响应了则为 true * HasRestarted - 当上传连接断掉后,系统会重新上传,此种情况则为 true */ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Threading; using System.Threading.Tasks; using Windows.Networking.BackgroundTransfer; using Windows.Storage; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.Web; namespace XamlDemo.BackgroundTask { public sealed partial class TransferUpload : Page { // 上传任务的集合 private ObservableCollection<TransferModel> _transfers = new ObservableCollection<TransferModel>(); // 所有上传任务的关联的 CancellationTokenSource 对象 private CancellationTokenSource _cancelToken = new CancellationTokenSource(); public TransferUpload() { this.InitializeComponent(); Init(); } private async void Init() { listView.ItemsSource = _transfers; // 获取全部上传任务 await LoadUploadAsync(); } // 加载全部上传任务 private async Task LoadUploadAsync() { IReadOnlyList<UploadOperation> uploads = null; try { // 获取所有后台上传任务 uploads = await BackgroundUploader.GetCurrentUploadsAsync(); } catch (Exception ex) { lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; return; } if (uploads.Count > 0) { List<Task> tasks = new List<Task>(); foreach (UploadOperation upload in uploads) { // 监视指定的后台上传任务 tasks.Add(HandleUploadAsync(upload, false)); } await Task.WhenAll(tasks); } } // 新增一个上传任务(一次请求上传一个文件) private async void btnAddUpload_Click(object sender, RoutedEventArgs e) { // 上传服务的地址 Uri serverUri = new Uri("http://localhost:39629/UploadFile.aspx", UriKind.Absolute); StorageFile sourceFile; try { // 文件源 sourceFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Logo.png", UriKind.Absolute)); } catch (Exception ex) { lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; return; } // 实例化 BackgroundUploader,并设置 http header BackgroundUploader backgroundUploader = new BackgroundUploader(); backgroundUploader.SetRequestHeader("Filename", "Logo.png"); // 创建一个后台上传任务,此任务包含一个上传文件 UploadOperation upload = backgroundUploader.CreateUpload(serverUri, sourceFile); // 以流的方式创建一个后台上传任务 // await backgroundUploader.CreateUploadFromStreamAsync(Uri uri, IInputStream sourceStream); // 监视指定的后台上传任务 await HandleUploadAsync(upload, true); } // 新增一个上传任务(一次请求上传多个文件) private async void btnAddMultiUpload_Click(object sender, RoutedEventArgs e) { // 上传服务的地址 Uri serverUri = new Uri("http://localhost:39629/UploadFile.aspx", UriKind.Absolute); // 需要上传的文件源集合 List<StorageFile> sourceFiles = new List<StorageFile>(); for (int i = 0; i < 3; i++) { StorageFile sourceFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///Assets/Logo.png", UriKind.Absolute)); sourceFiles.Add(sourceFile); } // 构造需要上传 BackgroundTransferContentPart 集合 List<BackgroundTransferContentPart> contentParts = new List<BackgroundTransferContentPart>(); for (int i = 0; i < sourceFiles.Count; i++) { BackgroundTransferContentPart contentPart = new BackgroundTransferContentPart("File" + i, sourceFiles[i].Name); contentPart.SetFile(sourceFiles[i]); contentParts.Add(contentPart); } // 创建一个后台上传任务,此任务包含多个上传文件 BackgroundUploader backgroundUploader = new BackgroundUploader(); UploadOperation upload = await backgroundUploader.CreateUploadAsync(serverUri, contentParts); // 监视指定的后台上传任务 await HandleUploadAsync(upload, true); } /// <summary> /// 监视指定的后台上传任务 /// </summary> /// <param name="upload">后台上传任务</param> /// <param name="isNew">是否是新增的任务</param> private async Task HandleUploadAsync(UploadOperation upload, bool isNew) { try { // 将 UploadOperation 附加到 TransferModel,以便上传进度可通知 TransferModel transfer = new TransferModel(); transfer.UploadOperation = upload; transfer.Source = "多个文件"; transfer.Destination = upload.RequestedUri.ToString(); transfer.Progress = "0 / 0"; _transfers.Add(transfer); lblMsg.Text += "Task Count: " + _transfers.Count.ToString(); lblMsg.Text += Environment.NewLine; // 当上传进度发生变化时的回调函数 Progress<UploadOperation> progressCallback = new Progress<UploadOperation>(UploadProgress); if (isNew) await upload.StartAsync().AsTask(_cancelToken.Token, progressCallback); // 新增一个后台上传任务 else await upload.AttachAsync().AsTask(_cancelToken.Token, progressCallback); // 监视已存在的后台上传任务 // 上传完成后获取服务端的响应信息 ResponseInformation response = upload.GetResponseInformation(); lblMsg.Text += "Completed: " + response.ActualUri + "-" + response.StatusCode.ToString(); lblMsg.Text += Environment.NewLine; } catch (TaskCanceledException) // 调用 CancellationTokenSource.Cancel() 后会抛出此异常 { lblMsg.Text += "Canceled: " + upload.Guid; lblMsg.Text += Environment.NewLine; } catch (Exception ex) { // 将异常转换为 WebErrorStatus 枚举,如果获取到的是 WebErrorStatus.Unknown 则说明此次异常不是涉及 web 的异常 WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult); lblMsg.Text += ex.ToString(); lblMsg.Text += Environment.NewLine; } finally { _transfers.Remove(_transfers.First(p => p.UploadOperation == upload)); } } // 进度发生变化时,更新 TransferModel 的 Progress,以便通知 private void UploadProgress(UploadOperation upload) { TransferModel transfer = _transfers.First(p => p.UploadOperation == upload); transfer.Progress = upload.Progress.BytesSent.ToString("#,0") + " / " + upload.Progress.TotalBytesToSend.ToString("#,0"); } // 取消全部后台上传任务 private void btnCancel_Click(object sender, RoutedEventArgs e) { _cancelToken.Cancel(); _cancelToken.Dispose(); _cancelToken = new CancellationTokenSource(); } } }
用于接收上传文件的服务
WebServer/UploadFile.aspx.cs
/* * 用于接收上传文件的服务 */ using System; using System.IO; namespace WebServer { public partial class UploadFile : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { try { if (Request.Headers["Filename"] != null) { // 接收单个文件,并保存 string fileName = Request.Headers["Filename"]; string saveLocation = Server.MapPath("UploadFiles") + "\\" + fileName.Insert(fileName.IndexOf("."), "_" + Guid.NewGuid().ToString()); using (System.IO.FileStream fs = new System.IO.FileStream(saveLocation, FileMode.Create)) { Request.InputStream.CopyTo(fs); } } else { // 接收多个文件,并保存 for (int i = 0; i < Request.Files.Count; i++) { var file = Request.Files[i]; if (file.ContentLength > 0) { string fileName = System.IO.Path.GetFileName(file.FileName); string saveLocation = Server.MapPath("UploadFiles") + "\\" + fileName.Insert(fileName.IndexOf("."), "_" + Guid.NewGuid().ToString()); file.SaveAs(saveLocation); } } } } catch (Exception ex) { Trace.Write(ex.Message); Response.StatusCode = 500; Response.StatusDescription = ex.Message; Response.End(); } } } }
OK
[源码下载]