How to await an awaitable object while observing a CancellationToken? - c#

I have an awaitable object which is NOT a Task (for example, an IObservable<T> with RX installed). I want to create a Task, which will end up as cancelled if the supplied CancellationToken is cancelled or return the result of the awaitable object otherwise. I came up with the following code:
public static Task ObserveTokenAsync(CancellationToken token)
{
TaskCompletionSource<Unit> tcs = new TaskCompletionSource<Unit>();
token.Register(() => tcs.SetCanceled());
return tcs.Task;
}
public static async Task<T> WrapObservableAsync<T>(IObservable<T> obs)
{
return await obs;
}
public static async Task<T> AwaitWhileObservingTokenAsync<T>(IObservable<T> obs, CancellationToken token)
{
var obsTask = WrapObservableAsync(obs);
var tokenTask = ObserveTokenAsync(token);
await Task.WhenAny(obsTask, tokenTask).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
return obsTask.Result;
}
With this approach I will need to overload/rewrite the last two methods for each of the awaitable types I will be using. Is there any better way to do this? My guess is that the INotifyCompletion interface may somehow be useful.
Also, can the ObserveTokenAsync method cause some kind of resource/memory leaks if the supplied tokens won't get cancelled? If yes, will setting the TaskCompletionSource as finished at the end of the AwaitWhileObservingTokenAsync method will be a good way to fix it?

Use the TaskObservableExtensions.ToTask extension method from Rx. It takes a CancellationToken and will take the last element from the observable when available to finish the returned task. When the CancellationToken is canceled the returned task will throw an OperationCanceledException when awaited. If the observable does not contain any elements, the returned task will throw an exception when awaited (probably an InvalidOperationException though I would have to look that up).

Related

How to fix the inconsistent cancellation behavior of Task.Run?

The Task.Run method has overloads that accept both sync and async delegates:
public static Task Run (Action action);
public static Task Run (Func<Task> function);
Unfortunately these overloads don't behave the same when the delegate throws an OperationCanceledException. The sync delegate results in a Faulted task, and the async delegate results in a Canceled task. Here is a minimal demonstration of this behavior:
CancellationToken token = new(canceled: true);
Task taskSync = Task.Run(() => token.ThrowIfCancellationRequested());
Task taskAsync = Task.Run(async () => token.ThrowIfCancellationRequested());
try { Task.WaitAll(taskSync, taskAsync); } catch { }
Console.WriteLine($"taskSync.Status: {taskSync.Status}"); // Faulted
Console.WriteLine($"taskAsync.Status: {taskAsync.Status}"); // Canceled (undesirable)
Output:
taskSync.Status: Faulted
taskAsync.Status: Canceled
Online demo.
This inconsistency has been observed also in this question:
Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested
That question asks about the "why". My question here is how to fix it. Specifically my question is: How to implement a variant of the Task.Run with async delegate, that behaves like the built-in Task.Run with sync delegate? In case of an OperationCanceledException it should complete asynchronously as Faulted, except when the supplied cancellationToken argument matches the token stored in the exception, in which case it should complete as Canceled.
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default);
Here is some code with the desirable behavior of the requested method:
CancellationToken token = new(canceled: true);
Task taskA = TaskRun2(async () => token.ThrowIfCancellationRequested());
Task taskB = TaskRun2(async () => token.ThrowIfCancellationRequested(), token);
try { Task.WaitAll(taskA, taskB); } catch { }
Console.WriteLine($"taskA.Status: {taskA.Status}");
Console.WriteLine($"taskB.Status: {taskB.Status}");
Desirable output:
taskA.Status: Faulted
taskB.Status: Canceled
One way to solve this problem is to use the existing Task.Run method, and attach a ContinueWith continuation that alters its cancellation behavior. The continuation returns either the original task or a faulted task, so it returns a Task<Task>. Eventually this nested task is unwrapped to a simple Task, with the help of the Unwrap method:
/// <summary>
/// Queues the specified work to run on the thread pool, and returns a proxy
/// for the task returned by the action. The cancellation behavior is the same
/// with the Task.Run(Action, CancellationToken) method.
/// </summary>
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(action);
return Task.Run(action, cancellationToken).ContinueWith(t =>
{
if (t.IsCanceled)
{
CancellationToken taskToken = new TaskCanceledException(t).CancellationToken;
if (taskToken != cancellationToken)
return Task.FromException(new OperationCanceledException(taskToken));
}
return t;
}, CancellationToken.None, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
}
Online demo.
The TaskCanceledException constructor is used in order to get access to the CancellationToken stored inside the canceled Task. Unfortunately the Task.CancellationToken property is not exposed (it is internal), but fortunately this workaround exists.

C#: How to correctly execute auto-generated methods from web service with a timeout in an async method?

I need to call some methods from a web service, specified in WSDL. VisualStudio created the corresponding methods for me, including async variants. The documentation specifies (different) timeouts for those methods, some of them could take several minutes. So what is the best way to implement this?
I have two approaches, but I'm not sure which one is better, or if I should do something completely different:
Variant 1: use generated async method and task.Wait instead of await:
public async Task<ResultType> MyMethod1Async()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(60000);
Task<ResultType> task = MyWebService.getSomeObjectAsync();
task.Wait(cts.Token);
return task.Result;
}
Variant 2: execute generated synchronous method in Task.Run:
public async Task<ResultType> MyMethod2Async()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(60000);
Task<ResultType> task = Task.Run(
() => MyWebService.getSomeObject(),
cts.Token);
return await task;
}
Neither option will do what you want.
Variant 1 will block on task.Result regardless of any timeout. Variant 2 will not be cancelled once the method has started running
If the async task does not support cancellation, the best you can do is to return to the caller when the timeout is reached, and let the task continue in the background, any eventual result will be ignored. For example:
public async Task<ResultType> MyMethodAsync<T>(TimeSpan timeout)
{
var task = SomeAsyncMethod<ResultType>();
var timeoutTask = Task.Delay(timeout);
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == timeoutTask)
{
// Handle timeout somehow
throw new TimeoutException("...");
}
return task.Result;
}
This is obviously not appropriate for compute bound tasks, and if it is possible to use real support for cancellation it should be used.

How can I implement a lazy TaskCompletionSource?

If the Task exposed by my TaskCompletionSource may never get called how can I deffer computation of the Result unless and until someone awaits the task?
For example, I want to block other async threads of execution until a ManualResetEvent is signaled using the following function WaitOneAsync. I complete the TaskCompleationSource in the callback of ThreadPool.RegisterWaitForSingleObject which happens when the WaitHandle is signaled. But if no one awaits the task then I don't want to RegisterWaitForSingleObject (nor do I want to RegisterWaitForSingleObject if the task is awaited after the WaitHandle is signaled).
How can I change WaitOneAsync so that the work to compute the result, to RegisterWaitForSingleObject, only happens after someone awaits the TaskCompleationSource.Task?
I believe the answer may lie in a custom TaskAwaiter as described here Implement AsyncManualResetEvent using Lazy<T> to determine if the task has been awaited by Scott Chamberlain but I can't quite get from his example to my solution... :(
public static async Task<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result) {
var tcs = new TaskCompletionSource<T>();
RegisteredWaitHandle rwh = null;
rwh = ThreadPool.RegisterWaitForSingleObject(
waitObject: waitHandle,
callBack: (s, t) => {
rwh.Unregister(null);
tcs.TrySetResult(result());
},
state: null,
millisecondsTimeOutInterval: -1,
executeOnlyOnce: true
);
return await tcs.Task;
}
As usr said, it's not possible to do something in reaction to a Task being awaited. But if you're okay with using a custom awaitable, then you can.
An easy way to do that is to use AsyncLazy from Stephen Cleary's AsyncEx:
private static Task<T> WaitOneAsyncImpl<T>(WaitHandle waitHandle, Func<T> result)
{
if (waitHandle.WaitOne(0))
return Task.FromResult(result());
var tcs = new TaskCompletionSource<T>();
RegisteredWaitHandle rwh = null;
rwh = ThreadPool.RegisterWaitForSingleObject(
waitObject: waitHandle,
callBack: (s, t) =>
{
rwh.Unregister(null);
tcs.TrySetResult(result());
},
state: null,
millisecondsTimeOutInterval: -1,
executeOnlyOnce: true
);
return tcs.Task;
}
public static AsyncLazy<T> WaitOneAsync<T>(this WaitHandle waitHandle, Func<T> result)
=> new AsyncLazy<T>(() => WaitOneAsyncImpl(waitHandle, result));
This is not possible exactly as you described your requirements. The TPL does not provide an event or a callback when someone adds a continuation or waits on a task.
So you need to structure the API so that only tasks that are needed are actually produced. What about this?
public static Func<Task<T>> CreateWaitOneAsyncFactory<T>(this WaitHandle waitHandle, Func<T> result) {
return () => WaitOneAsync(waitHandle, result);
}
This returns a task factory instead of a task. This is cheating but the only possible solutions involve cheating of this kind.
You can return a custom awaitable as well. But that would not involve tasks at all and it misses the composability features of tasks. Awaitables mostly are a C# concept. Exposing them can result in unclean APIs.
Unrelated to your question: You can remove the await tcs.Task and return that task directly. Also, the result function is not needed. Return a Task that has no result. Callers can then add a result if they wish. This makes the API of WaitOneAsync cleaner.

How to cancel a task from a TaskCompletionSource?

I'm trying to create an async ProducerConsumerCollection and for that, I'm using this msdn page (http://msdn.microsoft.com/en-us/library/hh873173.aspx (bottom of the page)).
I'm now trying to add a timeout, here is what I do :
public async Task<T> TakeWithTimeout(int timeout)
{
Task<T> takeTask = this.Take();
if (timeout <= 0 || takeTask == await Task.WhenAny(this.tasks.Take(), Task.Delay(timeout)))
{
return await takeTask;
}
else
{
// Timeout
return default(T);
}
}
}
The problem with this code is that, in case of timeout, it does not cancel the task created by the Take() method.
Since this task has been "created" by the TaskCompletionSource, I cannot give it a cancellationToken?
So, how to proceed to cancel it and properly implement this Take with timeout ?
Thanks :)
Writing a cancel-safe async-friendly producer/consumer collection is non-trivial. What you need to do is change Take to accept a CancellationToken as a parameter, and it should register a handler so that when it is cancelled the TaskCompletionSource is cancelled.
I highly recommend you use BufferBlock<T>, which has cancellation support built-in.
If you can't use TPL Dataflow (e.g., you're working in a PCL or have target platforms unsupported by Dataflow), then you can use the producer/consumer collections in my open-source AsyncEx library (such as AsyncProducerConsumerQueue or AsyncCollection). These are both based on AsyncLock and AsyncConditionVariable, a design I describe briefly on my blog (which does not get into the cancellation details). The key behind supporting cancellation in a producer/consumer collection with this design is to support cancellation in AsyncConditionVariable.WaitAsync; once your condition variable type supports cancellation, then your collection will easily support it, too.
I'm just going to post my solution to the question How to cancel a task from a TaskCompletionSource because that is what I needed myself.
I'm guessing this could be used for your specific need, but it's not tied to a specific timeout functionality, so this is a general solution (or so I hope).
This is an extension method:
public static async Task WaitAsync<T>(this TaskCompletionSource<T> tcs, CancellationToken ctok)
{
CancellationTokenSource cts = null;
CancellationTokenSource linkedCts = null;
try {
cts = new CancellationTokenSource();
linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, ctok);
var exitTok = linkedCts.Token;
Func<Task> listenForCancelTaskFnc = async () => {
await Task.Delay(-1, exitTok).ConfigureAwait(false);
};
var cancelTask = listenForCancelTaskFnc();
await Task.WhenAny(new Task[] { tcs.Task, cancelTask }).ConfigureAwait(false);
cts.Cancel();
} finally {
if(linkedCts != null) linkedCts.Dispose();
}
}
Usage:
async Task TestAsync(CancellationToken ctok) {
var tcs = new TaskCompletionSource<bool>();
if (somethingOrTheOther) {
tcs.TrySetResult(true);
}
await tcs.WaitAsync(ctok);
}
The idea is to have a supervisory async Task waiting essentially forever until it is cancelled, which we can use to 'exit early' in case the TaskCompletionSource is not yet satisfied, but we need to exit anyhow due to a cancel request.
The supervisory Task is guaranteed to be cancelled at the end of WaitAsync regardless how it falls out of the WhenAny. Either the TaskCompletionSource is satisfied with a result, and WhenAny completes, briefly leaving the supervisory sleeper task in tact until the next line where cts.Cancel() is called, or it was cancelled with the exitToken, which is a combined token of the passed in ctok or the internal one cts.Token.
Anyhow, I hope this makes sense -- please let me know if this code has any problems...

Return Task instead of Task<TResult> from TaskCompletionSource

As I have seen in several coding examples, as well as, what I can understand from this SO question I should be able to return a non-generic Task from TaskCompletionSource
(i.e., Return Task and not Task<TResult> from the method UploadFilesAsync)
Yet the following code:
public async Task UploadFilesAsync(string fileAPath, string fileBPath)
{
var tcs = new TaskCompletionSource<Object>();
//logic to process files
try
{
await Task.WhenAll(uploadFileAAsync(fileAPath),
uploadFileBAsync(fileBPath));
tcs.TrySetResult(null);
}
catch (Exception e)
{
tcs.SetException(e);
}
finally
{
//logic to clean up files
}
return tcs.Task;
}
Produces the following syntax error
'UploadFilesAsync(string, string)' is an async method that returns 'Task',
a return keyword must not be followed by an object expression.
Did you intend to return 'Task<T>'?
I am targeting .NET 4.5. I know it can work to return Task(of object) but that makes the API feel "dirty". Is it preferred practice to return Task(of object) or is it possible to return Task (non-generic as shown in the code)?
I know it can work to return Task(of object)
Well, that won't do what you expect it to.
The trouble is that you're trying to return a task... and an async method automatically wraps the return value in another task. It's not clear why you're using an async method at all here, to be honest. Why not just write this:
public Task UploadFilesAsync(string fileAPath, string fileBPath)
{
return Task.WhenAll(uploadFileAAsync(fileAPath), uploadFileBAsync(fileBPath));
}
Does that not do what you want? You just want a task which completes when both of the "suboperations" have completed, right? That's exactly what Task.WhenAll returns. Your method is still non-blocking - it won't wait until the operations have completed before it returns. It's just that you're using the fact that Task.WhenAll is non-blocking to achieve that, instead of an async method.
EDIT: Note that if you wanted to do something else in that method as well, you could make it an async method without using TaskCompletionSource yourself:
public async Task UploadFilesAsync(string fileAPath, string fileBPath)
{
// Upload the files
await Task.WhenAll(uploadFileAAsync(fileAPath), uploadFileBAsync(fileBPath));
await SomethingElseAsync();
MaybeDoSomethingCheap();
}
Note that even though the async method here returns Task, you don't have a return value - the async/await machinery handles all of that for you, returning a task which will complete when MaybeDoSomethingCheap() has finished, or fault if an exception is thrown.
As far as I know there is no direct way to return a Task object when employing TaskCompletionSource<T>.
Generally I prefer to return a Task<bool> type object at these situations. But you are right that, returning a generic type object makes no sense if return values of the function is not usable.
But in fact you do not need to create a TaskCompletionSource since you have an await keyword inside the function. TaskCompletionSource is generally used to convert an synchronous function to an asynchronous one. Since you are already calling an asynchronous function (and in fact this seems as the only functionality) you do not need to create a TaskCompletionSource.
public async Task UploadFilesAsync(string fileAPath, string fileBPath)
{
return await Task.WhenAll(uploadFileAAsync(fileAPath), uploadFileBAsync(fileBPath));
}

Categories