posts - 615, comments - 10484, trackbacks - 594, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

公告

背水一战 Windows 10 (118) - 后台任务: 后台下载任务(任务分组,并行或串行执行,组完成后通知)

Posted on 2018-06-22 08:45 webabcd 阅读(...) 评论(...) 编辑 收藏

[源码下载]


背水一战 Windows 10 (118) - 后台任务: 后台下载任务(任务分组,并行或串行执行,组完成后通知)



作者:webabcd


介绍
背水一战 Windows 10 之 后台任务

  • 后台下载任务(任务分组,并行或串行执行,组完成后通知)



示例
演示后台下载任务的分组,以及如何设置组内任务是并行执行还是串行执行,以及组任务全部完成后如何 toast 或 tile 通知)
BackgroundTask/TransferModel.cs

/*
 * 扩展了 DownloadOperation 和 UploadOperation,用于 MVVM 绑定数据
 */

using System;
using System.ComponentModel;
using Windows.Networking.BackgroundTransfer;

namespace Windows10.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));
            }
        }
    }
}

BackgroundTask/TransferGroup.xaml

<Page
    x:Class="Windows10.BackgroundTask.TransferGroup"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:Windows10.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="10 0 10 10">

            <ScrollViewer Name="scrollViewer" Height="100" Margin="5">
                <TextBlock Name="lblMsg" TextWrapping="Wrap" />
            </ScrollViewer>

            <Button Name="btnAddDownload" Content="新增一组(3 个)下载任务,可以指定其是并发还是串行,当这一组的所有任务都完成后弹出通知" Margin="5" Click="btnAddDownload_Click" />
            <Button Name="btnCancel" Content="取消所有下载任务" Margin="5" Click="btnCancel_Click" />

            <ListView Name="listView" Height="286" Padding="5">
                <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/TransferGroup.xaml.cs

/*
 * 演示后台下载任务的分组,以及如何设置组内任务是并行执行还是串行执行,以及组任务全部完成后如何 toast 或 tile 通知)
 * 
 * BackgroundTransferGroup - 后台下载任务的分组对象
 *     static BackgroundTransferGroup CreateGroup(string name) - 创建指定分组标识的 BackgroundTransferGroup 对象
 *     Name - 分组标识(只读)
 *     TransferBehavior - 组内下载任务的执行方式,BackgroundTransferBehavior 枚举
 *         Parallel - 并行
 *         Serialized - 串行
 * 
 * BackgroundDownloader - 后台下载任务管理器
 *     TransferGroup - 设置或获取分组对象(BackgroundTransferGroup 类型)
 *     static GetCurrentDownloadsForTransferGroupAsync(BackgroundTransferGroup group) - 获取指定组的所有下载任务
 *     
 * DownloadOperation - 下载任务对象
 *     TransferGroup - 获取此下载任务的分组对象(BackgroundTransferGroup 类型)
 */

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;
using Windows.UI.Notifications;
using Windows.Data.Xml.Dom;

namespace Windows10.BackgroundTask
{
    public sealed partial class TransferGroup : Page
    {
        // 用于后台任务的分组(通过组名标识后台任务)
        private BackgroundTransferGroup _group = BackgroundTransferGroup.CreateGroup("my_group");

        // 下载任务的集合
        private ObservableCollection<TransferModel> _transfers = new ObservableCollection<TransferModel>();

        // 所有下载任务的关联的 CancellationTokenSource 对象
        private CancellationTokenSource _cancelToken = new CancellationTokenSource();

        public TransferGroup()
        {
            this.InitializeComponent();

            Init();
        }

        private async void Init()
        {
            // 指定组内任务并行执行
            _group.TransferBehavior = BackgroundTransferBehavior.Parallel;

            listView.ItemsSource = _transfers;

            // 加载指定组的下载任务
            await LoadDownloadAsync();
        }

        // 加载指定组的下载任务
        private async Task LoadDownloadAsync()
        {
            IReadOnlyList<DownloadOperation> downloads = null;
            try
            {
                // 获取指定组的下载任务
                downloads = await BackgroundDownloader.GetCurrentDownloadsForTransferGroupAsync(_group);
            }
            catch (Exception ex)
            {
                WriteLine(ex.ToString());
                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);
            }
        }

        // 新增一组(3 个)下载任务
        private async void btnAddDownload_Click(object sender, RoutedEventArgs e)
        {
            BackgroundDownloader backgroundDownloader = new BackgroundDownloader();
            // 指定分组
            backgroundDownloader.TransferGroup = _group;
            // 组任务全部成功后弹出指定的 toast 通知(类似的还有 SuccessTileNotification, FailureToastNotification, FailureTileNotification)
            backgroundDownloader.SuccessToastNotification = GetToastNotification(_group.Name);

            List<DownloadOperation> downloads = new List<DownloadOperation>();
            for (int i = 0; i < 3; i++)
            {
                Uri sourceUri = new Uri("http://files.cnblogs.com/webabcd/Windows10.rar", UriKind.Absolute);

                StorageFile destinationFile;
                try
                {
                    // 保存的目标地址(别忘了在 Package.appxmanifest 中配置好 <Capability Name="documentsLibrary" /> 和 .rar 类型文件的关联)
                    StorageFolder storageFolder = await KnownFolders.GetFolderForUserAsync(null, KnownFolderId.DocumentsLibrary);
                    destinationFile = await storageFolder.CreateFileAsync("Windows10.rar", CreationCollisionOption.GenerateUniqueName);
                }
                catch (Exception ex)
                {
                    WriteLine(ex.ToString());
                    return;
                }
                // 创建一个后台下载任务
                DownloadOperation download = backgroundDownloader.CreateDownload(sourceUri, destinationFile);

                downloads.Add(download);
            }

            // 处理并监视组内的后台下载任务
            Task[] tasks = new Task[downloads.Count];
            for (int i = 0; i < downloads.Count; i++)
            {
                tasks[i] = HandleDownloadAsync(downloads[i], true);
            }

            await Task.WhenAll(tasks);

        }

        /// <summary>
        /// 处理并监视组内的后台下载任务
        /// </summary>
        /// <param name="download">后台下载任务</param>
        /// <param name="isNew">是否是新增的任务</param>
        private async Task HandleDownloadAsync(DownloadOperation download, bool isNew)
        {
            try
            {
                // 构造显示用的相关数据
                TransferModel transfer = new TransferModel();
                transfer.DownloadOperation = download;
                transfer.Source = download.RequestedUri.ToString();
                transfer.Destination = download.ResultFile.Path;
                transfer.Progress = download.Progress.Status.ToString() + ": 0 / 0";

                _transfers.Add(transfer);

                WriteLine("Task Count: " + _transfers.Count.ToString());

                // 当下载进度发生变化时的回调函数
                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();
                WriteLine("Completed: " + response.ActualUri + ", HttpStatusCode: " + response.StatusCode.ToString());
            }
            catch (TaskCanceledException) // 调用 CancellationTokenSource.Cancel() 后会抛出此异常
            {
                WriteLine("Canceled: " + download.Guid);
            }
            catch (Exception ex)
            {
                // 将异常转换为 WebErrorStatus 枚举,如果获取到的是 WebErrorStatus.Unknown 则说明此次异常不是涉及 web 的异常
                WebErrorStatus error = BackgroundTransferError.GetStatus(ex.HResult);

                WriteLine(ex.ToString());
            }
            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.Status.ToString() + ": " + download.Progress.BytesReceived.ToString("#,0") + " / " + download.Progress.TotalBytesToReceive.ToString("#,0");
        }

        // 取消全部后台下载任务
        private void btnCancel_Click(object sender, RoutedEventArgs e)
        {
            _cancelToken.Cancel();
            _cancelToken.Dispose();

            _cancelToken = new CancellationTokenSource();
        }

        // 向 lblMsg 中追加一行文本
        private void WriteLine(string message)
        {
            lblMsg.Text += message;
            lblMsg.Text += Environment.NewLine;

            scrollViewer.ChangeView(0, scrollViewer.ScrollableHeight, 1f);
        }

        private ToastNotification GetToastNotification(string groupName)
        {
            string toastXml = $@"
                <toast activationType='foreground'>
                    <visual>
                        <binding template='ToastGeneric'>
                            <text>toast - title</text>
                            <text>组 {groupName} 中的下载任务全部完成了</text>
                        </binding>
                    </visual>
                </toast>";

            XmlDocument toastDoc = new XmlDocument();
            toastDoc.LoadXml(toastXml);

            return new ToastNotification(toastDoc);
        }
    }
}



OK
[源码下载]