【问题标题】:Cancel task and wait for it to finish取消任务并等待它完成
【发布时间】:2013-02-14 19:02:28
【问题描述】:

我有一个耗时的任务,我需要在单独的线程中运行以避免锁定 GUI 线程。随着此任务的进行,它会更新特定的 GUI 控件。

问题是用户可能会在任务结束之前移动到 GUI 的另一部分,在这种情况下,我必须:

  1. 取消正在进行的任务(如果它处于活动状态)
  2. 等到它完成取消:这很关键,因为耗时任务的目标是更新特定控件。如果多个线程同时尝试执行此操作,事情可能会变得一团糟。
  3. 从头开始启动任务

举个具体的例子,假设表单有两个部分:一个用于导航目录树,另一个用于显示缩略图。当用户导航到另一个目录时,缩略图需要刷新。

首先我想使用BackgroundWorkerAutoResetEvent 来等待取消,但我一定搞砸了,因为取消时我陷入僵局。然后我读到了 TPL,它应该取代 BGW 和更原始的机制。

这可以使用 TPL 轻松完成吗?

【问题讨论】:

    标签: c# task-parallel-library


    【解决方案1】:

    需要注意的几点:

    • 您可以从CancellationTokenSource 获取CancellationToken

    • 任务取消是一个合作动作:如果您的任务没有定期检查CancellationToken.IsCancellationRequested属性,那么无论您尝试取消任务多少次,它都会快乐地翻滚。

    说了这么多,大意如下:

    void Main()
    {
        var tokenSource = new CancellationTokenSource();
        var myTask = Task.Factory
            .StartNew(() => DoWork(tokenSource.Token), tokenSource.Token);
    
        Thread.Sleep(1000);
    
        // ok, let's cancel it (well, let's "request it be cancelled")
        tokenSource.Cancel();
    
        // wait for the task to "finish"
        myTask.Wait();
    }
    
    public void DoWork(CancellationToken token)
    {
        while(!token.IsCancellationRequested)
        {
            // Do useful stuff here
            Console.WriteLine("Working!");
            Thread.Sleep(100);
        }
    }
    

    【讨论】:

    • 是否可以只创建一次令牌源并每次都将其令牌传递给StartNew
    • 把它想象成一个孩子的两个罐子放在一根绳子上(好吧,一到 N 罐:你有一个末端,所有开始的任务都可以。你在末端喊“取消”,所有这些共享令牌会听到它。
    • @dario_ramos 我相信您评论中的问题已在评论here 中得到回答,但为了完整起见,答案是否定的 - CancellationTokenSource 不能被重置和再次取消;如果您要求取消其中一个,那么如果您想再次取消,则必须用新的替换它。
    • @bohdan_trotsenko 该示例使用同步调用的非异步方法。在同步方法中,正确的等待方式是Thread.Sleep
    • 当心。 Task.Wait 将抛出 AggregateException 指示任务已取消(您已经知道,因为您取消了它)。 docs.microsoft.com/en-us/dotnet/api/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-07
    • 2021-10-21
    • 2018-08-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多