C#.net 5 使用取消令牌操作下载任务

Posted

技术标签:

【中文标题】C#.net 5 使用取消令牌操作下载任务【英文标题】:C#.net 5 Manipulating download tasks with cancellation token 【发布时间】:2021-08-03 20:14:46 【问题描述】:

有人可以帮我理解任务吗? 我有下载并想在用户关闭应用程序时使用取消令牌取消所有当前下载

我是这样做的,但我不确定它是否正确......

令牌控制器声明:

CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ConcurrentBag<Task> downloadTasks = new ConcurrentBag<Task>();

任务初始化:

downloadTasks.Add(Task.Run(() => SaveStreamAsFile(pathToSave, streamInfo, fileToSave, token)));

下载方式:

public static bool SaveStreamAsFile(string filePath, Stream inputStream, string fileName, CancellationToken ct) 

    try 
        DirectoryInfo info = new DirectoryInfo(filePath);
        if (!info.Exists) 
            info.Create();
        

        string path = Path.Combine(filePath, fileName);
        using (FileStream outputFileStream = new FileStream(path, FileMode.Create)) 
            inputStream.CopyTo(outputFileStream);
            if (ct.IsCancellationRequested) 
                ct.ThrowIfCancellationRequested();
                return false;
            
        
        return true;
     catch (Exception ex) 
        // log the exception 
    
    return false;

取消任务方法:

public static async Task<bool> cancelAllTasks() 
    if (downloadTasks.Count == 0) return true;

    tokenSource.Cancel();

    try 
        await Task.WhenAll(downloadTasks.ToArray()).ConfigureAwait(false);
     catch (OperationCanceledException) 
        // task canceled log
        return false;
     finally 
        tokenSource.Dispose();
    

    return true;

表单关闭方法:

private async void Form1_FormClosing(object sender, FormClosingEventArgs e) 
    var canExit = await cancelAllTasks();
    if (!canExit) 
        e.Cancel = true;
        return;
    

表单关闭方法让我很困惑,因为如果 canExit 为 false,用户不能永远关闭表单??

好吧,如果有人能给我指出使用任务的正确方法,我将不胜感激,因为我已经阅读了文档和示例,但 C# 中的任务对我来说还很难理解。 谢谢

【问题讨论】:

【参考方案1】:

我有下载,想在用户关闭应用程序时取消所有当前下载

好消息:您实际上不必做任何事情。如果您的应用程序退出,则下载会自动取消。

完全删除您的 Form1_FormClosing 事件处理程序,它应该可以正常工作。

【讨论】:

我在想这个,但我不知道,框架会为我完成所有工作吗? @GnRSlashSP:不会。操作系统会。【参考方案2】:

我的建议是,如果操作员试图关闭表单,您的应用程序的反应应该类似于操作员想要关闭记事本应用程序时记事本的反应:

如果所有任务都已完成:关闭 部分任务未完成:警告操作员部分任务将被取消。 如果操作员对此表示满意:取消任务,完成后关闭表单 如果操作员对此不满意:取消关闭表单。

要正确执行此操作,您应该让您的方法可等待:

public async Task<bool> SaveStreamAsFileAsync(string filePath, Stream inputStream,
    string fileName, CancellationToken cancellationToken)


    try
    
        Directory.CreateDirectory(filePath);
        // does nothing if already exists

        string path = Path.Combine(filePath, fileName);
        using (var stream = File.Create(path)
        
            await inputStream.CopyToAsync(stream, cancellationToken);
        

        // if here, stream completely copied to the file
        return true;
    
    catch (OperationCancelledException exc)
    
         // TODO: how to react if operation cancelled?
         // TODO: check if the file must be deleted
    
    catch (Exception ex)  // every other exception
    
        // TODO: log
    
    return false; // stream not saved

您的表单有两个属性:DownLoadTask 和 CancellationTokenSource:

private Task<bool> TaskDownLoad get; set; = Task.FromResult<false>();
private CancellationTokenSource CancellationTokenSource get; set; = null;

private CancellationTokenSource CreateCancellationTokenSource()

     return new CancellationTokenSource();

我使用一个单独的方法来创建 CancellationTokenSource。如果需要,您可以添加一个检查以查看是否已经存在 CancellationTokenSource,并决定如果存在该怎么办:仅 Dispose 它并创建一个新的?取消创建新源并重用当前源?

我需要一个额外的布尔属性:IsCloseRequested。如果为 true,则表单必须在下载任务完成或取消后立即关闭。

private bool IsCloseRequested get; set; = false;

开始和取消下载:

private async Task DownloadAsync()

    // Show the operator that downloading is about to start, for instance ajax loader
    // also: prevent extra start by disabling buttons and menu items
    this.ShowDownloadStarted();

    string folder = ...;
    string fileName = ...;

    using (this.cancellationTokenSource = this.CreateCancellationTokenSource())
    
        using (Stream source = ...)
        
            bool fileSaved = await this.SaveStreamAsFileAsync(folder, source,
                fileName, this.cancellationTokenSource.Token);
            if (fileSaved)
            
                ...
            
            else
            
                ...
            
        
    
    this.cancellationTokenSource = null;
    this.ShowDownloadFinished();

    if (this.IsCloseRequested)
    
        this.Close();
    

在下载时,您需要断言不会开始第二次下载,因此请禁用可以开始第二次下载的按钮/菜单项。同时向操作员显示已开始下载。

private void CancelDownload()

    this.CancellationTokenSource?.Cancel();

我希望取消您的下载不会花费大量时间。如果没有,请考虑向操作员显示请求取消:沙漏光标,甚至可能是面板中的一些信息。

例如因为 ButtonClick 开始下载:

private async void OnButtonStartDownload_Clicked(object sender, ...)

    await this.DownloadAsync();


private void OnButtonCancelDownload_Clicked(object sender, ...)

    this.CancelDownload();

我在事件处理程序中做的不多,也许您还有其他方法可以开始下载,例如通过单击菜单或事件表单加载。另外:也许你想要一个按钮来开始和取消下载。

回到你的问题

在这些更改之后关闭表单很容易:

private bool IsTaskBusy => !this.TaskDownLoad.IsCompleted;

private void OnFormClosing(object sender, FormClosingEventArgs e)
(
    if (this.IsTaskBusy)
    
        // We can't close right now; we'll have to wait until task is finished
        e.Cancel = true;

        // warn operator and ask if cancellation is allowed
        bool cancellationAllowed = this.AskIfCancellationAllowed();
        if (cancellationAllowed)
        
            this.CancelDownload();
            this.IsCloseRequested = true;
        
    
    // else: no task running: we can continue closing

因此,如果操作员在任务仍在运行时尝试关闭表单,则表单不会关闭。使用 MessageBox 询问操作员是否允许取消。

如果不是:不要关闭表单 如果允许取消:也不要关闭表单,而是取消任务并记住请求关闭。

每当任务完成时,无论是因为取消还是因为完成,都会调用Close()。这次没有正在运行的任务,所以表单会立即关闭。

这种方式的好处是操作者也可以选择取消下载而不关闭表单。

【讨论】:

以上是关于C#.net 5 使用取消令牌操作下载任务的主要内容,如果未能解决你的问题,请参考以下文章

.NET 4 任务类教程

在服务结构服务中存储取消令牌

使用单个取消令牌添加中止所有任务

使用 NSURLSession 如何在取消下载任务时获取接收到的数据或临时文件位置

如何取消 CancellationToken

使用任务取消令牌停止 TCP 侦听器