I have this small test in .NET 6, which works fine (it's green).
Func<CancellationToken, Task> taskFactory = async token => throw new OperationCanceledException();
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
However, the compiler (rightfully?) complains: warning CS1998: This async method lacks 'await' operators and will run synchronously. I could not figure out a way to transform this into a synchronous variant.
I tried these 2 options for the lambda
without async: token => throw new OperationCanceledException(). It's pretty clear to me that this will just throw the exception directly on the stack instead of wrapping it in the task, but this is what the IDE suggested.
token => Task.FromException(new OperationCanceledException()). This goes to IsFaulted instead of IsCanceled.
What is the correct way to transfrom this to a synchronous variant?
Edit: The point of this snippet was to test whether some CUT deals correctly with the OperationCanceledException being emitted from a taskFactory. So the exception still needs to emitted, possibly thrown, in the solution. I am not simply looking for a way to set a task to canceled.
You could await a completed task:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.CompletedTask;
throw new OperationCanceledException();
};
Assert.True(taskFactory(CancellationToken.None).IsCanceled);
However your test does have a race condition, although it's likely to usually succeed. But if you change it to this:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.Delay(1000);
throw new OperationCanceledException();
};
var task = taskFactory(CancellationToken.None);
Console.WriteLine(task.IsCanceled);
it will print false. To fix that, you have to wait for the task to complete:
Func<CancellationToken, Task> taskFactory = async token =>
{
await Task.Delay(1000);
throw new OperationCanceledException();
};
var task = taskFactory(CancellationToken.None);
Task.WaitAny(task);
Console.WriteLine(task.IsCanceled);
Note that I'm using Task.WaitAny() to wait for the task to complete without throwing a TaskCanceledException. If you simply await task; it will of course throw that exception.
You could use the low-level APIs as well to achieve the desired outcome in a synchronous way.
TaskCompletionSource tcs = new();
Func<CancellationToken, Task> taskFactory = token => tcs.Task;
With this approach your delegate remains sync (don't need to use the async keyword)
CancellationTokenSource cts = new(0);
cts.Token.Register(() => tcs.TrySetCanceled());
Here we connect the Task and Cancellation primitives via a simple callback.
We cancel the Task right away.
And that's it :) A working example can be found here.
Related
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.
I've done this Unit Test and I don't understand why the "await Task.Delay()" doesn't wait !
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
Here is the Unit Test output :
How is this possible an "await" doesn't wait, and the main thread continues ?
new Task(async () =>
A task does not take a Func<Task>, but an Action. It will call your asynchronous method and expect it to end when it returns. But it does not. It returns a task. That task is not awaited by the new task. For the new task, the job is done once the method returned.
You need to use the task that already exists instead of wrapping it in a new task:
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Func<Task> asyncMethod = async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
};
Console.WriteLine("Main.BeforeStart");
Task myTask = asyncMethod();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
The problem is that you are using the non-generic Task class, that is not meant to produce a result. So when you create the Task instance passing an async delegate:
Task myTask = new Task(async () =>
...the delegate is treated as async void. An async void is not a Task, it cannot be awaited, its exception cannot be handled, and it's a source of thousands of questions made by frustrated programmers here in StackOverflow and elsewhere. The solution is to use the generic Task<TResult> class, because you want to return a result, and the result is another Task. So you have to create a Task<Task>:
Task<Task> myTask = new Task<Task>(async () =>
Now when you Start the outer Task<Task> it will be completed almost instantly because its job is just to create the inner Task. You'll then have to await the inner Task as well. This is how it can be done:
myTask.Start(TaskScheduler.Default);
Task myInnerTask = await myTask;
await myInnerTask;
You have two alternatives. If you don't need an explicit reference to the inner Task then you can just await the outer Task<Task> twice:
await await myTask;
...or you can use the built-in extension method Unwrap that combines the outer and the inner tasks into one:
await myTask.Unwrap();
This unwrapping happens automatically when you use the much more popular Task.Run method that creates hot tasks, so the Unwrap is not used very often nowadays.
In case you decide that your async delegate must return a result, for example a string, then you should declare the myTask variable to be of type Task<Task<string>>.
Note: I don't endorse the use of Task constructors for creating cold tasks. As a practice is generally frowned upon, for reasons I don't really know, but probably because it is used so rarely that it has the potential of catching other unaware users/maintainers/reviewers of the code by surprise.
General advice: Be careful everytime you are supplying an async delegate as an argument to a method. This method should ideally expect a Func<Task> argument (meaning that understands async delegates), or at least a Func<T> argument (meaning that at least the generated Task will not be ignored). In the unfortunate case that this method accepts an Action, your delegate is going to be treated as async void. This is rarely what you want, if ever.
[Fact]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(() =>
{
Console.WriteLine("Task.BeforeDelay");
Task.Delay(3000).Wait();
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.True(isOK, "OK");
}
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).
I read about the Cancellation methods but based on my understanding they all provide control over tasks from outside the task, but if i want to cancel a task from the inside.
For example in this pseudo code:
Task tasks = new Task(() =>
{
bool exists = CheckFromDB();
if (!exists)
break;
}
Can i cancel a task from the inside?
The only idea i got is to trigger an exception inside the task and handle it from outside but surly that is not the way to do.
If you want to truly Cancel the task (e.g. after it's completed the Status property would be set to Canceled), you can do it this way:
var cts = new CancellationTokenSource();
var token = cts.Token;
Task innerCancel = new Task(
() =>
{
if (!CheckFromDB())
{
cts.Cancel();
}
token.ThrowIfCancellationRequested();
},
token);
When you use return task won't be actually canceled, but rather code after the return statement won't be executed, and the task itself will have its state as RanToCompletion.
On a side note it's advised to use Task.Factory.StartNew for .NET 4 and Task.Run for .NET 4.5 instead of constructor to create tasks.
The lambda expression code inside a task behaves just the same as in any other method. If you want to end the task for some reason you can just simply return:
Task task = Task.Run(() =>
{
if(!CheckFromDB())
{
return;
}
}
You can also throw an exception. That will end the task and mark it as faulted but there's no reason to do so if you can avoid it. If you are really responding to some problem that you can't overcome, then yes, just throw an exception.
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...