简体   繁体   English

如何在C#中实现取消共享任务:s

[英]How to implement cancellation of shared Task:s in C#

All, here is a question about design/best practices of a complex case of canceling Task:s in C#. 所有这些都是关于在C#中取消任务的复杂案例的设计/最佳实践的问题。 How do you implement cancellation of a shared task? 如何实现共享任务的取消?

As a minimal example, lets assume the following; 作为一个最小的例子,我们假设以下内容; we have a long running, cooperatively cancellable operation 'Work'. 我们有一个长期运行,可合作取消的“工作”操作。 It accepts a cancellation token as argument and throws if it has been canceled. 它接受取消令牌作为参数,如果已被取消则抛出。 It operates on some application state and returns a value. 它在某个应用程序状态下运行并返回一个值。 Its result is independently required by two UI components. 其结果由两个UI组件独立完成。

While the application state is unchanged, the value of the Work function should be cached, and if one computation is ongoing, a new request should not start a second computation but rather start waiting for a result. 虽然应用程序状态不变,但应该缓存Work函数的值,如果一个计算正在进行,则新请求不应该开始第二次计算,而是开始等待结果。

Either of the UI components should be able to cancel it's Task without affecting the other UI components task. 任何一个UI组件都应该能够取消它的任务,而不会影响其他UI组件任务。

Are you with me so far? 你到目前为止和我在一起吗?

The above can be accomplished by introducing an Task cache that wraps the real Work task in TaskCompletionSources, whose Task:s are then returned to the UI components. 上面的内容可以通过在TaskCompletionSources中引入一个包装真实Work任务的Task缓存来完成,其任务:然后返回给UI组件。 If a UI component cancels it's Task, it only abandons the TaskCompletionSource Task and not the underlying task. 如果UI组件取消了它的Task,它只会放弃TaskCompletionSource Task而不是底层任务。 This is all good. 这一切都很好。 The UI components creates the CancellationSource and the request to cancel is a normal top down design, with the cooperating TaskCompletionSource Task at the bottom. UI组件创建CancellationSource,取消请求是正常的自顶向下设计,底部是协作TaskCompletionSource任务。

Now, to the real problem. 现在,到了真正的问题。 What to do when the application state changes? 应用程序状态更改时该怎么办? Lets assume that having the 'Work' function operate on a copy of the state is not feasible. 让我们假设让“工作”功能在状态副本上运行是不可行的。

One solution would be to listen to the state change in the task cache (or there about). 一种解决方案是监听任务缓存中的状态变化(或那里)。 If the cache has a CancellationToken used by the underlying task, the one running the Work function, it could cancel it. 如果缓存具有底层任务使用的CancellationToken,即运行Work函数的任务,则可以取消它。 This could then trigger a cancellation of all attached TaskCompletionSources Task:s, and thus both UI components would get Canceled tasks. 然后,这可以触发取消所有附加的TaskCompletionSources Task:s,因此两个UI组件都将获得Canceled任务。 This is some kind of bottom up cancellation. 这是一种自下而上的取消。

Is there a preferred way to do this? 有没有一种首选的方法呢? Is there a design pattern that describes it some where? 是否有一种设计模式将其描述在哪里?

The bottom up cancellation can be implemented, but it feel a bit weird. 可以实现自下而上取消,但感觉有点奇怪。 The UI-task is created with a CancellationToken, but it is canceled due to another (inner) CancellationToken. UI任务是使用CancellationToken创建的,但由于另一个(内部)CancellationToken而被取消。 Also, since the tokens are not the same, the OperationCancelledException can't just be ignored in the UI - that would (eventually) lead to an exception being thrown in the outer Task:s finalizer. 此外,由于令牌不相同,因此不能在UI中忽略OperationCancelledException - 这会(最终)导致在外部Task:s终结器中抛出异常。

It sounds like you want a greedy set of task operations - you have a task result provider, and then construct a task set to return the first completed operation, ex: 听起来你想要一组贪婪的任务操作 - 你有一个任务结果提供者,然后构造一个任务集来返回第一个完成的操作,例如:

// Task Provider - basically, construct your first call as appropriate, and then 
//   invoke this on state change

public void OnStateChanged()
{
    if(_cts != null)
       _cts.Cancel();

    _cts = new CancellationTokenSource();
    _task = Task.Factory.StartNew(() =>
       {
           // Do Computation, checking for cts.IsCancellationRequested, etc
           return result;
       });
}

// Consumer 1

var cts = new CancellationTokenSource();
var task = Task.Factory.StartNew(() =>
  {
       var waitForResultTask = Task.Factory.StartNew(() =>
          {
              // Internally, this is invoking the task and waiting for it's value
              return MyApplicationState.GetComputedValue();
          });

       // Note this task cares about being cancelled, not the one above
       var cancelWaitTask = Task.Factory.StartNew(() =>
         {
              while(!cts.IsCancellationRequested)
                 Thread.Sleep(25);

              return someDummyValue;
         });

       Task.WaitAny(waitForResultTask, cancelWaitTask);

       if(cancelWaitTask.IsComplete)
          return "Blah"; // I cancelled waiting on the original task, even though it is still waiting for it's response
       else
          return waitForResultTask.Result;
  });

Now, I haven't fully tested this, but it should allow you to "cancel" waiting on a task by cancelling the token (and thus forcing the "wait" task to complete first and hit the WaitAny ), and allow you to "cancel" the computation task. 现在,我还没有对此进行全面测试,但它应该允许您通过取消令牌来“取消”等待任务(从而强制“等待”任务先完成并点击WaitAny ),并允许您“取消“计算任务。

The other thing is to figure out a clean way of making the "cancel" task wait without horrible blocking. 另一件事是想出一个干净的方法让“取消”任务等待没有可怕的阻止。 It's a good start I think. 我认为这是一个好的开始。

Here's my attempt: 这是我的尝试:

// the Task for the current application state
Task<Result> _task;
// a CancellationTokenSource for the current application state
CancellationTokenSource _cts;

// called when the application state changes
void OnStateChange()
{
    // cancel the Task for the old application state
    if (_cts != null)
    {
        _cts.Cancel();
    }

    // new CancellationTokenSource for the new application state
    _cts = new CancellationTokenSource();
    // start the Task for the new application state
    _task = Task.Factory.StartNew<Result>(() => { ... }, _cts.Token);
}

// called by UI component
Task<Result> ComputeResultAsync(CancellationToken cancellationToken)
{
    var task = _task;
    if (cancellationToken.CanBeCanceled && !task.IsCompleted)
    {
        task = WrapTaskForCancellation(cancellationToken, task);
    }
    return task;
}

with

static Task<T> WrapTaskForCancellation<T>(
    CancellationToken cancellationToken, Task<T> task)
{
    var tcs = new TaskCompletionSource<T>();
    if (cancellationToken.IsCancellationRequested)
    {
        tcs.TrySetCanceled();
    }
    else
    {
        cancellationToken.Register(() =>
        {
            tcs.TrySetCanceled();
        });
        task.ContinueWith(antecedent =>
        {
            if (antecedent.IsFaulted)
            {
                tcs.TrySetException(antecedent.Exception.GetBaseException());
            }
            else if (antecedent.IsCanceled)
            {
                tcs.TrySetCanceled();
            }
            else
            {
                tcs.TrySetResult(antecedent.Result);
            }
        }, TaskContinuationOptions.ExecuteSynchronously);
    }
    return tcs.Task;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM