如何在不变丑的情况下使方法可以取消?

Jen*_*ens 47 c# multithreading cancellation-token

我目前正在改进我们长期运行的方法,以便取消.我打算使用System.Threading.Tasks.CancellationToken来实现它.

我们的方法通常执行一些长时间运行的步骤(主要向硬件发送命令然后等待硬件),例如

void Run()
{
    Step1();
    Step2();    
    Step3();
}
Run Code Online (Sandbox Code Playgroud)

我对取消的第一个(也许是愚蠢的)想法会将其转化为

bool Run(CancellationToken cancellationToken)
{
    Step1(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    Step2(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;    

    Step3(cancellationToken);

    if (cancellationToken.IsCancellationRequested)
        return false;

    return true;
}
Run Code Online (Sandbox Code Playgroud)

坦率地看起来很可怕.这种"模式"也将在单个步骤内继续(并且它们必然已经相当长).这会使Thread.Abort()看起来很性感,虽然我知道它不推荐.

有没有更清晰的模式来实现这一点,不会隐藏许多样板代码下面的应用程序逻辑?

编辑

作为步骤性质的一个例子,该Run方法可以阅读

void Run()
{
    GiantRobotor.MoveToBase();
    Oven.ThrowBaguetteTowardsBase();    
    GiantRobotor.CatchBaguette();
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我们正在控制需要同步的不同硬件单元才能协同工作.

Mat*_*ten 33

如果这些步骤以某种方式独立于方法中的数据流,但不能以并行的方式执行,则以下方法可能更易读:

void Run()
{
    // list of actions, defines the order of execution
    var actions = new List<Action<CancellationToken>>() {
       ct => Step1(ct),
       ct => Step2(ct),
       ct => Step3(ct) 
    };

    // execute actions and check for cancellation token
    foreach(var action in actions)
    {
        action(cancellationToken);

        if (cancellationToken.IsCancellationRequested)
            return false;
    }

    return true;
}
Run Code Online (Sandbox Code Playgroud)

如果这些步骤不需要取消令牌,因为您可以将它们分成很小的单元,您甚至可以编写一个较小的列表定义:

var actions = new List<Action>() {
    Step1, Step2, Step3
};
Run Code Online (Sandbox Code Playgroud)


Nic*_*sen 23

怎么样延续?

var t = Task.Factory.StartNew(() => Step1(cancellationToken), cancellationToken)
   .ContinueWith(task => Step2(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current)
   .ContinueWith(task => Step3(cancellationToken), cancellationToken, TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Current);
Run Code Online (Sandbox Code Playgroud)

  • 很好的答案,这是任务擅长的. (2认同)

Mik*_*bel 9

我承认它不漂亮,但指导是做你做过的事情:

if (cancellationToken.IsCancellationRequested) { /* Stop */ }
Run Code Online (Sandbox Code Playgroud)

......或略短:

cancellationToken.ThrowIfCancellationRequested()
Run Code Online (Sandbox Code Playgroud)

通常,如果您可以将取消令牌传递给各个步骤,则可以将签出分散,以免它们使代码饱和.您也可以选择不检查消除不断 ; 如果您正在执行的操作是幂等的并且不是资源密集型的,那么您不一定要在每个阶段检查取消.最重要的检查时间是在返回结果之前.

如果您将令牌传递给所有步骤,则可以执行以下操作:

public static CancellationToken VerifyNotCancelled(this CancellationToken t) {
    t.ThrowIfCancellationRequested();
    return t;
}

...

Step1(token.VerifyNotCancelled());
Step2(token.VerifyNotCancelled());
Step3(token.VerifyNotCancelled());
Run Code Online (Sandbox Code Playgroud)

  • 此外 - 在迭代重要数据项(例如,复制文件的操作中的每个文件)之后检查取消,并且只有在您可以保证成功清理时才检查. (2认同)

Jim*_*hel 7

当我不得不做这样的事情时,我创建了一个委托来做:

bool Run(CancellationToken cancellationToken)
{
    var DoIt = new Func<Action<CancellationToken>,bool>((f) =>
    {
        f(cancellationToken);
        return cancellationToken.IsCancellationRequested;
    });

    if (!DoIt(Step1)) return false;
    if (!DoIt(Step2)) return false;
    if (!DoIt(Step3)) return false;

    return true;
}
Run Code Online (Sandbox Code Playgroud)

或者,如果步骤之间从未有任何代码,您可以写:

return DoIt(Step1) && DoIt(Step2) && DoIt(Step3);
Run Code Online (Sandbox Code Playgroud)