How to implement canceling a joint task: s in C #

That's it, here's the design / best practice question for the difficult case of canceling Task: s in C #. How do you cancel a common task?

As a minimal example, let’s say the following: we have a long-running, joint operation “Work”. It takes a cancellation token as an argument and returns if it was canceled. It works in a certain state of the application and returns a value. Its result is independently required by two user interface components.

As long as the application state does not change, the value of the Work function should be cached, and if one calculation continues, the new query should not start the second calculation, but rather will wait for the result.

Any of the user interface components should be able to cancel its Task, without affecting the other task of the user interface components.

Are you still with me

The above can be accomplished by entering a task cache that wraps the real Work Task in TaskCompletionSources, whose task: s is then returned to the user interface components. If the user interface component cancels its "Task", it leaves only the TaskCompletionSource task and not the main task. This is all good. The user interface components create the CancellationSource, and the cancellation request is a normal design from top to bottom, and below is a working TaskCompletionSource Task.

Now, to the real problem. What to do when the state of the application changes? Assume that using the Work function on a state copy is not possible.

One solution would be to listen for a state change in the task cache (or there). If the cache has a CancellationToken application used by the main task, the one that performs the Work function can cancel it. Then this can lead to the cancellation of all connected TaskCompletionSources Task: s, and thus both components of the user interface will receive the canceled tasks. This is a kind of failure from the bottom up.

Is there a preferred way to do this? Is there a design pattern that describes it somewhere?

A bottom-up reduction can be implemented, but this is a bit strange. A UI task is created using a CancellationToken, but is canceled due to another (internal) CancellationToken. In addition, since the tokens do not match, the OperationCancelledException cannot simply be ignored in the user interface, which (ultimately) will cause the exception to be thrown in the external Task: s finalizer.

+6
source share
2 answers

It looks like you need a greedy task set - you have a task provider, and then create a task set to return the first completed operation, for example:

// 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 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 response else return waitForResultTask.Result; }); 

Now I have not fully tested this, but it should allow you to “cancel” the wait for the task by canceling the token (and thus force the completion of the “wait” task and press WaitAny ), and allow to “cancel” the calculation task.

Another thing is to figure out how to make the "cancel" task without a terrible block. I think this is a good start.

+1
source

Here is 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; } 

from

 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; } 
+1
source

Source: https://habr.com/ru/post/916279/


All Articles