暂停 Task.Factory.StartNew 中的任务

Posted

技术标签:

【中文标题】暂停 Task.Factory.StartNew 中的任务【英文标题】:Pausing a task within Task.Factory.StartNew 【发布时间】:2015-03-26 15:28:59 【问题描述】:

在我的 Asp.Net MVC 5 项目中,我有一个大约 3 分钟的任务,我将其传递给 Task.Factory.StartNew()。

如果在任务中运行的代码的某个步骤中存在验证问题,我想从任务中暂停该任务。我不想异步延迟它,因为任务的其余部分将继续运行,这是不可能的。

由于我在任务中,我可以使用 thread.sleep() 而不产生任何影响吗?我读到我可能必须使用 TaskScheduler.Default 让 Task.Factory 为每个任务创建一个新线程。

我正在使用类似于 CancellationToken 的 PauseToken,因此我将能够根据用户输入恢复任务或取消此任务。

多线程真的让我害怕,我不想忽略一些东西。

这是一个 Thread.Sleep 实现的示例:

public void WaitIfPaused(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)
    
        //TODO: If paused for an hour notify user via noty and abort the task to keep it from completing by cancellation via cancellationToken.
        //wait for 1 hour
        for (int i = 0; i < 3600; i++)
        
            ThrowExceptionIfCancelled(cancellationToken, progressBar);
            if (pauseToken.IsPaused)
            
                Thread.Sleep(1000);
            
            else
            
                break;
            

        
    

PauseToken:http://blogs.msdn.com/b/pfxteam/archive/2013/01/13/cooperatively-pausing-async-methods.aspx


请求:在共享代码库中实现任务结构。

public void StartTask(params object[] data)
    
        //throw an exception if no ITask was found
        if (_taskToRun == null)
            throw new Exception("Task cannot be null");

        //set up task cancellation
        CancellationTokenSource = new CancellationTokenSource();
        var cancellationToken = CancellationTokenSource.Token;

        //set up task pausing
        PauseTokenSource = new PauseTokenSource();
        var pauseToken = PauseTokenSource.Token;

        //start a new task using the Task that was set
        _task = Task.Factory.StartNew(() => _taskToRun.Execute(cancellationToken, pauseToken, data), cancellationToken);

_taskToRun.Execute 调用的我的 Execute 方法:

Public override void Execute(CancellationToken cancellationToken, PauseToken pauseToken, params object[] data)
    
        var managerList = (List<IFileManager>) data[0];
        var instr = (List<InstructionSet>) data[1];

        ProcessInstructions(managerList, instr, cancellationToken, pauseToken);
    

由于 cmets 的更新:

代码示例:3 条指令

For(var instruction in instructions)

    instruction.Execute();

在我的执行方法中,我遇到了暂停场景并从执行中调用 WaitWhilePausedAsync。它将继续执行其他两条指令,但暂停当前唯一的指令执行方法。

编辑:通过等待 instruction.Execute() 它将等到 instruction.Execute() 完成或取消暂停。


最终编辑: 我能够通过等待 Execute 方法并像 Servy 和 I3arnon 建议的那样使其异步来解决问题。

最终代码示例:

foreach(var instruction in instructions)

    try 
    
        await instruction.Execute(pauseToken);
    
    catch(InvalidOperationException)
    
        pauseTokenSource.IsPaused = true;
        //ask if user wants to cancel or resume.
    

//Simplified
public async Task<bool> Execute(PauseToken pauseToken)

    await pauseToken.WaitWhilePausedAsync();
    //do work

【问题讨论】:

您链接到的模式似乎很适合您。为什么不使用它? 因此,您无需等待即可找到一种工具,该工具可以完全以尽可能有效的方式完成您想要的工作,然后您选择只是不使用它并在布尔值上旋转等待?只需拨打WaitWhilePausedAsyncawait 即可,就像博文中展示的那样。这正是它的初衷。 我不能使用 WaitWhilePausedAsync 因为我希望从内部暂停任务。例如 Execute 也应该是异步的,或者您只需要使用同步而不是异步的信号原语。如果你想使用同步方法来做同步的事情,你为什么要特意找到一个异步信号原语?跨度> @BrandonTull:你真正想解决什么问题?对于 ASP.NET MVC 应用程序,StartNew 几乎可以肯定是错误的方法。 【参考方案1】:

您可以放心地使用Thread.Sleep。唯一的缺点是线程会浪费在同步阻塞上。

您应该改用await Task.Delay(1000)。该行之后的代码在等待完成之前不会执行,但在此期间您不会浪费线程:

public async Task WaitIfPausedAsync(PauseToken pauseToken, CancellationToken cancellationToken, IProgressBar progressBar)

    for (int i = 0; i < 3600; i++)
    
        ThrowExceptionIfCancelled(cancellationToken, progressBar);
        if (pauseToken.IsPaused)
        
            await Task.Delay(1000)
        
        else
        
            break;
        
    

编辑:我不知道PauseToken.WaitWhilePausedAsync。您绝对应该使用它,而不是通过轮询 PauseToken.IsPaused 来复制自己

【讨论】:

或者他可以只是await WaitWhilePausedAsync,这完全符合他的要求,不会等待超过所需的整秒,不会浪费线程在这个过程中,并且在语义上代表了确切的问题。您将竭尽全力不使用旨在解决手头确切问题的工具,而是使用更糟糕的解决方案。 我在原始问题的底部添加了为什么我无法在指令中执行异步暂停。Execute() 方法。 @Servy 当然.. 我不知道那种方法。 @BrandonTull 您需要将这些方法标记为asyncawait 返回的Task。这将使您能够在不阻塞线程的情况下进行顺序处理。 @BrandonTull 这并不意味着您不能使用异步。您似乎在混合同步和顺序。但是,如果您必须同步,请使用同步暂停令牌

以上是关于暂停 Task.Factory.StartNew 中的任务的主要内容,如果未能解决你的问题,请参考以下文章

Task.Run 和Task.Factory.StartNew 区别

Task.Factory.StartNew 测试

Task.Factory.StartNew 和 Task.Run 到底有什么区别?

Task.Factory.StartNew的用法

Task.Run() 和 Task.Factory.StartNew() 有啥区别

Task.Run 和 Task.Factory.StartNew 区别