背水一戰 Windows 10 (119) - 後臺任務: 後臺下載任務(任務分組,組完成後觸發後臺任務)

[源碼下載]


html

背水一戰 Windows 10 (119) - 後臺任務: 後臺下載任務(任務分組,組完成後觸發後臺任務)



做者:webabcd


介紹
背水一戰 Windows 10 之 後臺任務html5

  • 後臺下載任務(任務分組,組完成後觸發後臺任務)



示例
演示後臺下載任務的分組,以及組任務所有完成後如何觸發後臺任務
/BackgroundTaskLib/BackgroundTaskTransfer.csc++

/*
 * 後臺任務,用於演示指定的一組後臺下載任務所有完成後如何觸發此後臺任務
 * 
 * BackgroundTransferCompletionGroup - 分組對象(用於實現「組任務所有完成後觸發後臺任務」)
 *     Enable() - 啓用「組任務所有完成後觸發後臺任務」的功能
 *     IsEnabled - 是否啓用了「組任務所有完成後觸發後臺任務」的功能(只讀)
 *     Trigger - 「組任務所有完成後觸發後臺任務」的觸發器
 *     
 * BackgroundDownloader - 後臺下載任務管理器
 *     BackgroundDownloader(BackgroundTransferCompletionGroup completionGroup) - 經過指定的 BackgroundTransferCompletionGroup 對象實例化 BackgroundDownloader 對象
 *     CompletionGroup - 獲取關聯的 BackgroundTransferCompletionGroup 對象
 *     
 * 
 * 注:須要在 Package.appxmanifest 添加「後臺任務」聲明,支持的任務類型選擇「系統事件」,並指定 EntryPoint(後臺任務的類全名),相似以下:
 * <Extension Category="windows.backgroundTasks" EntryPoint="BackgroundTaskLib.BackgroundTaskTransfer">
 *   <BackgroundTasks>
 *     <Task Type="systemEvent" />
 *   </BackgroundTasks>
 * </Extension>
 */

using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Data.Xml.Dom;
using Windows.Networking.BackgroundTransfer;
using Windows.UI.Notifications;

namespace BackgroundTaskLib
{
    public sealed class BackgroundTaskTransfer : IBackgroundTask
    {
        // 所註冊的後臺任務的名稱
        public static string TaskName { get; set; } = "Transfer";
        // 所註冊的後臺任務的 EntryPoint,即後臺任務的類全名
        public static string TaskEntryPoint { get; set; } = "BackgroundTaskLib.BackgroundTaskTransfer";

        // 實現 IBackgroundTask 接口,其只有一個方法,即 Run()
        public void Run(IBackgroundTaskInstance taskInstance)
        {
            // 獲取 BackgroundTransferCompletionGroupTriggerDetails 對象,若是不是 null 則說明是 BackgroundTransferCompletionGroup.Trigger 觸發的
            BackgroundTransferCompletionGroupTriggerDetails details = taskInstance.TriggerDetails as BackgroundTransferCompletionGroupTriggerDetails;
            if (details == null)
            {
                return;
            }

            // 獲取下載任務列表
            List<DownloadOperation> failedDownloads = new List<DownloadOperation>();
            int successTotal = 0;
            foreach (DownloadOperation download in details.Downloads)
            {
                if (IsFailed(download))
                {
                    // 保存失敗的下載任務列表,稍後會重試
                    failedDownloads.Add(download);
                }
                else
                {
                    successTotal++;
                }
            }

            if (failedDownloads.Count > 0)
            {
                // 從新下載失敗的任務
                RetryDownloads(failedDownloads);
            }

            // 此後臺任務執行完畢,彈出指定的 toast 通知
            ShowToast(successTotal, failedDownloads.Count);
        }

        // 判斷指定的下載任務是否失敗了
        private bool IsFailed(DownloadOperation download)
        {
            BackgroundTransferStatus status = download.Progress.Status;
            if (status == BackgroundTransferStatus.Error || status == BackgroundTransferStatus.Canceled)
            {
                return true;
            }

            ResponseInformation response = download.GetResponseInformation();
            if (response.StatusCode != 200)
            {
                return true;
            }

            return false;
        }

        // 從新下載指定的任務
        private void RetryDownloads(IEnumerable<DownloadOperation> downloads)
        {
            // 註冊指定的後臺任務,並返回與此後臺任務相關聯的 BackgroundDownloader 對象
            BackgroundDownloader downloader = BackgroundTaskTransfer.RegisterBackgroundTaskAndReturnBackgrounDownloader();

            foreach (DownloadOperation download in downloads)
            {
                // 建立並啓動後臺任務
                DownloadOperation downloadNew = downloader.CreateDownload(download.RequestedUri, download.ResultFile);
                Task<DownloadOperation> startTask = downloadNew.StartAsync().AsTask();
            }

            // 啓用「組任務所有完成後觸發後臺任務」的功能
            downloader.CompletionGroup.Enable();
        }

        // 註冊指定的後臺任務,並返回與此後臺任務相關聯的 BackgroundDownloader 對象
        // 後臺任務的註冊通常是在前臺代碼中寫的,可是因爲本例還有一個後臺任務執行完成後從新下載失敗任務的功能,因此這部分代碼寫在後臺代碼中就很合理了
        public static BackgroundDownloader RegisterBackgroundTaskAndReturnBackgrounDownloader()
        {
            // 根據指定 BackgroundTransferCompletionGroup 對象建立一個 BackgroundDownloader 對象
            BackgroundTransferCompletionGroup completionGroup = new BackgroundTransferCompletionGroup();
            BackgroundDownloader downloader = new BackgroundDownloader(completionGroup);

            // 註冊一個後臺任務,並指定觸發器爲 BackgroundTransferCompletionGroup.Trigger
            BackgroundTaskBuilder builder = new BackgroundTaskBuilder();
            builder.Name = BackgroundTaskTransfer.TaskName;
            builder.TaskEntryPoint = BackgroundTaskTransfer.TaskEntryPoint;
            builder.SetTrigger(completionGroup.Trigger);
            BackgroundTaskRegistration task = builder.Register();

            return downloader;
        }

        // 後臺任務執行完成後彈出 toast 通知
        private void ShowToast(int successTotal, int failureTotal)
        {
            string toastXml = $@"
                <toast activationType='foreground'>
                    <visual>
                        <binding template='ToastGeneric'>
                            <text>toast - title</text>
                            <text>下載任務成功數: {successTotal}</text>
                            <text>下載任務失敗數: {failureTotal}</text>
                        </binding>
                    </visual>
                </toast>";

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

            ToastNotificationManager.CreateToastNotifier().Show(new ToastNotification(toastDoc));
        }
    }
}

BackgroundTask/TransferModel.csweb

/*
 * 擴展了 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/TransferBackground.xamlexpress

<Page
    x:Class="Windows10.BackgroundTask.TransferBackground"
    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="btnAddDownloadAndRegister" Content="新增一組(3 個)下載任務,並註冊指定的後臺任務,當這一組下載任務所有完成後觸發一個後臺任務。" Margin="5" Click="btnAddDownloadAndRegister_Click" />
            <Button Name="btnRemoveDownloadAndUnregister" Content="取消下載任務並註銷指定的後臺任務" Margin="5" Click="btnRemoveDownloadAndUnregister_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/TransferBackground.xaml.cswindows

/*
 * 演示後臺下載任務的分組,以及組任務所有完成後如何觸發後臺任務
 * 
 * BackgroundTransferCompletionGroup - 分組對象(用於實現「組任務所有完成後觸發後臺任務」)
 *     Enable() - 啓用「組任務所有完成後觸發後臺任務」的功能
 *     IsEnabled - 是否啓用了「組任務所有完成後觸發後臺任務」的功能(只讀)
 *     Trigger - 「組任務所有完成後觸發後臺任務」的觸發器
 *     
 * BackgroundDownloader - 後臺下載任務管理器
 *     BackgroundDownloader(BackgroundTransferCompletionGroup completionGroup) - 經過指定的 BackgroundTransferCompletionGroup 對象實例化 BackgroundDownloader 對象
 *     CompletionGroup - 獲取關聯的 BackgroundTransferCompletionGroup 對象
 *     
 *     
 * 注:須要引用後臺任務項目,相關代碼參見 BackgroundTaskLib/BackgroundTaskTransfer.cs
 */

using BackgroundTaskLib;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Background;
using Windows.Networking.BackgroundTransfer;
using Windows.Storage;
using Windows.UI.Core;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Navigation;
using Windows.Web;

namespace Windows10.BackgroundTask
{
    public sealed partial class TransferBackground : Page
    {
        // 後臺任務是否已在系統中註冊
        private bool _taskRegistered = false;

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

        // 全部下載任務的關聯的 CancellationTokenSource 對象
        private CancellationTokenSource _cancelToken = new CancellationTokenSource();

        public TransferBackground()
        {
            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)
            {
                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);
            }
        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            // 遍歷全部已註冊的後臺任務
            foreach (KeyValuePair<Guid, IBackgroundTaskRegistration> task in BackgroundTaskRegistration.AllTasks)
            {
                if (task.Value.Name == BackgroundTaskTransfer.TaskName)
                {
                    _taskRegistered = true;
                    break;
                }
            }

            UpdateUI();
        }

        // 新增一組(3 個)下載任務,並註冊指定的後臺任務
        private async void btnAddDownloadAndRegister_Click(object sender, RoutedEventArgs e)
        {
            // 註冊指定的後臺任務,並返回與此後臺任務相關聯的 BackgroundDownloader 對象
            BackgroundDownloader downloader = BackgroundTaskTransfer.RegisterBackgroundTaskAndReturnBackgrounDownloader();
            _taskRegistered = true;
            UpdateUI();

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

                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 = downloader.CreateDownload(uri, destinationFile);

                downloads.Add(download);
            }

            // 啓用「組任務所有完成後觸發後臺任務」的功能
            downloader.CompletionGroup.Enable();

            WriteLine("用於完成後觸發後臺任務的一組下載任務建立完成了,相關的後臺任務也註冊了");

            // 處理並監視組內的後臺下載任務
            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");
        }

        // 向 lblMsg 中追加一行文本
        private void WriteLine(string message)
        {
            var ignore = lblMsg.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                lblMsg.Text += message;
                lblMsg.Text += Environment.NewLine;

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

        // 取消下載任務並註銷指定的後臺任務
        private void btnRemoveDownloadAndUnregister_Click(object sender, RoutedEventArgs e)
        {
            // 取消下載任務
            _cancelToken.Cancel();
            _cancelToken.Dispose();
            _cancelToken = new CancellationTokenSource();

            // 註銷指定的後臺任務
            foreach (KeyValuePair<Guid, IBackgroundTaskRegistration> task in BackgroundTaskRegistration.AllTasks)
            {
                if (task.Value.Name == BackgroundTaskTransfer.TaskName)
                {
                    // 從系統中註銷指定的後臺任務。惟一一個參數表明若是當先後臺任務正在運行中,是否須要將其取消
                    task.Value.Unregister(true);
                    break;
                }
            }
            _taskRegistered = false;
            UpdateUI();
        }

        private async void UpdateUI()
        {
            await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                btnAddDownloadAndRegister.IsEnabled = !_taskRegistered;
                btnRemoveDownloadAndUnregister.IsEnabled = _taskRegistered;
            });
        }
    }
}



OK
[源碼下載]app

相關文章
相關標籤/搜索