Imitation of OperationCanceledException scenario with unit-test fails - c#

I use Moq 4.18.2 framework for my tests.
The RtspClient might throw an OperationCanceledException from ConnectAsync. So, I try to test this scenario. My test below throws an exception System.OperationCanceledException: The operation was canceled. and the catch (OperationCanceledException) never gets executed. What am I doing wrong here?
RTSP
public interface IRtspClient : IDisposable
{
event EventHandler<RawFrame> FrameReceived;
Task ConnectAsync(CancellationToken token);
Task ReceiveAsync(CancellationToken token);
}
Method that uses IRtspClient
public async Task ConnectAsync(CancellationToken token = default)
{
try
{
await _rtspClient.ConnectAsync(token).ConfigureAwait(false);
OnConnected();
}
catch (OperationCanceledException ex)
{
OnConnectAttemptCanceled(ex);
throw;
}
catch(Exception ex)
{
OnFailedToConnect(ex);
throw;
}
}
Test
[TestMethod]
public async Task ConnectAsync_Canceled()
{
var expectedCanceledTaskStatus = true;
var tcs = new CancellationTokenSource();
tcs.Cancel();
var rtspClient = new Mock<IRtspClient>();
rtspClient
.Setup(_ => _.ConnectAsync(It.IsAny<CancellationToken>()))
.Returns(Task.FromException<OperationCanceledException>(new OperationCanceledException()))
var actualCanceledTaskStatus = false;
var camera = new MyCamera(rtspClient.Object);
camera.FailedToConnect += () => actualCanceledTaskStatus = true;
await camera.ConnectAsync(tcs.Token);
Assert.AreEqual(expectedCanceledTaskStatus, actualCanceledTaskStatus);
}
UPDATE
Added missing await as suggested by #Dai, but my test still fails. Can anyone take a look at the test code?

You need to await the returned Task inside the try{} block - otherwise
synchronous control will immediately leave the try{} block.
Exceptions thrown inside an anonymous function (or local function, or lambda method, or ye olde school delegate() local) will not be caught by the catch.
Also, CancellationTokenSource is IDisposable, so you should change your ConnectAsync_Canceled test to wrap it in a using() block.
Also, don't swallow exceptions - so my code below captures both exceptions for possible investigation and re-throws them with throw; (don't use throw ex;: it resets the recorded stack-trace; instead just do throw; by itself).
With OperationCanceledException and/or TaskCanceledException specifically, the exception needs to be re-thrown right back to the original caller (i.e. the owner of the CancellationTokenSource); whereas if a TaskCanceledException is caught and handled without the corresponding Task "seeing" then the caller will assume the operation succeeded despite the cancelation request.
Change your code to this:
public async Task ConnectAsync( CancellationToken cancellationToken = default )
{
try
{
await this.rtspClient.ConnectAsync(cancellationToken ).ConfigureAwait(false);
this.OnConnected();
}
catch( OperationCanceledException cEx )
{
this.OnConnectAttemptCanceled( cEx );
throw; // Re-throw so the `Task` representing *this method* (`ConnectAsync`) will report as Cancelled rather than Succeeded.
}
catch( Exception ex )
{
this.OnFailedToConnect( ex );
throw; // Re-throw so the `Task` representing *this method* (`ConnectAsync`) will report as Failed rather than Succeeded.
}
}

I found my mistake (in addition to what #Dai noticed). I should have either put await camera.ConnectAsync from my test in try-catch or used Assert.ThrowsExceptionAsync. I chose the latter. Here is the working test:
[TestMethod]
public async Task ConnectAsync_Canceled()
{
var expectedTaskCanceledStatus = true;
var rtspClient = new Mock<IRtspClient>();
rtspClient
.Setup(_ => _.ConnectAsync(default(CancellationToken)))
.Returns(Task.FromException(new OperationCanceledException()));
var actualTaskCanceledStatus = false;
var camera = new MyCamera(rtspClient.Object);
camera.ConnectAttemptCanceled += () => actualTaskCanceledStatus = true;
await Assert.ThrowsExceptionAsync<OperationCanceledException>(async () => await camera.ConnectAsync());
Assert.AreEqual(expectedTaskCanceledStatus, actualTaskCanceledStatus);
}

Related

Converting sync to async code got unhandled System.OperationCanceledException: 'The operation was canceled.'

I'm trying to convert my sync functions to async. In all my sync functions I have a cancellation token which is used on function, task and parallel blocks. I have a try/catch block before calling the async function, but I'm getting an unhandled exception:
Exception thrown: 'System.OperationCanceledException' in
System.Threading.Tasks.Parallel.dll An exception of type
'System.OperationCanceledException' occurred in
System.Threading.Tasks.Parallel.dll but was not handled in user code
The operation was canceled.
My async function:
public async Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) =>
await Task.Run(() => Decode(fileFullPath, fileDecodeType, progress), progress?.Token ?? default);
How I call it:
try
{
await SlicerFile.DecodeAsync(fileName, fileDecodeType, Progress);
}
catch (OperationCanceledException) { } // Do not work!
catch (Exception exception) // Works for other exceptions
{
await this.MessageBoxError(exception.ToString(), "Error opening the file");
}
catch (OperationCanceledException) is never reached nor catch (Exception exception) in a cancel event. As my try is at top most, why doesn't it catch the exception?
But if I do:
public async Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) =>
await Task.Run(() => throw new Exception("Test"));
I get the exception catch on the generic Exception (it's handled)
In other hand with old code it's working and handling the OperationCanceledException:
var task = await Task.Factory.StartNew( () =>
{
try
{
SlicerFile.Decode(fileName, fileDecodeType, Progress);
return true;
}
catch (OperationCanceledException) {} // Works!
catch (Exception exception)
{
Dispatcher.UIThread.InvokeAsync(async () =>
await this.MessageBoxError(exception.ToString(), "Error opening the file"));
}
return false;
});
What am I doing wrong?
The results of Task.Run don't need to be awaited right there necessarily. You can just return the running Task and then the method doesn't need to be awaited or be async any longer.
Task DecodeAsync(string? fileFullPath, FileDecodeType fileDecodeType, OperationProgress? progress = null) => Task.Run(() =>
Decode(fileFullPath, fileDecodeType, progress), progress?.Token ?? default);
And since you're passing in progress with a token, you can monitor that to exit the decode method cleanly instead of trying to catch and ignore the operation cancelled exception.
You'll have better luck if you make the decode method itself asynchronous if you can. It already returns a Task, so it could return a Task (or whatever). Your old code is also asynchronous in the same way, so there's no advantage to your new code that I can see.

Task swallows the exception thrown

In the method below, when an exception is thrown in the TRY block, it is being swallowed. How can I make it throw the exception so that it gets written to log in the catch block? The log writer works fine. Thanks!
public static bool MonitorQueueEmptyTask(string queueName, CancellationTokenSource tokenSource)
{
try
{
Task<bool> task = Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!"); //THIS THROW IS SWALLOWED -- NO LOG WRITTEN ON CATCH
};
return true;
}, tokenSource.Token);
}
catch (Exception ex)
{
WriteExceptionToLog(ex.Stack); //it's not that this method doesn't work. it works fine.
return false;
}
return true;
}
If you want to fire and forget, you can attach a continuation using ContinueWith. The current try-catch will not help you at all, as the exception is encapsulated inside the Task. If this is "fire and forget", than you can log the exception:
public static Task MonitorQueueEmptyTask(
string queueName, CancellationTokenSource tokenSource)
{
return Task.Factory.StartNew<bool>(() =>
{
while (!QueueManager.IsQueueEmpty(queueName))
{
if (tokenSource.IsCancellationRequested)
{
break;
}
Thread.Sleep(5000);
throw new Exception("Throwing an error!");
};
}, tokenSource.Token, TaskCreationOptions.LongRunning).ContinueWith(faultedTask =>
{
WriteExceptionToLog(faultedTask.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
This, in turn, will not propagate the exception after it's thrown, but will provide a mechanism to log the error. If you want the exception to be properly handled, you can register to TaskScheduler.UnobservedTaskException. Additionally, you can set ThrowUnobservedTaskExceptions enabled="true" in your configuration if you want unhandled exceptions to terminate your application. ContinueWith will consider the exception "handled" once you look at the task.Exception property.
The exception is not swallowed; it's just that it doesn't occur on the thread that executes the try/catch block, but on the separate Task thread.
If you don't observe the task's result or exception, when the task is eventually garbage collected, it will throw an exception saying that the task was not observed. Unless you catch that by handling the TaskScheduler.UnobservedTaskException, it will crash the process.
I also had a problem with this, and i really dislike the whole idea of App.config, so can provide another solution to prevent the exceptions disappearing :)
Save the exception then throw it after the Task.Run has completed, e.g.
private async void Function() {
Exception save_exception = null;
await Task.Run(() => {
try {
// Do Stuff
} catch (Exception ex) {
save_exception = ex;
}
}).ContinueWith(new Action<Task>(task => {
if (save_exception != null)
throw save_exception;
// Do Stuff
}));
}

Task Factory Method with Delay

I'm trying to call a particular method with 5 mins delay:
try
{
HttpContext ctx = HttpContext.Current;
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
HttpContext.Current = ctx;
System.Threading.Thread.Sleep(5 * 60 * 1000);
Sendafter5mins(param1,params2);
});
}
catch (Exception EX)
{
//Log Exception if any
}
This method is failing silently sometimes without any exception in log.
Please Suggest me is this the right way to fire a method with 5 mins delay.
Since you don't await the Task, nor Wait() on it, any exception thrown from Sendafter5mins(..) will not get caught in your catch block. If you are not using .NET 4.5, this should fail the whole process since the exception will fail the finalizer thread. Change your code to:
try
{
HttpContext ctx = HttpContext.Current;
System.Threading.Tasks.Task.Factory.StartNew(() =>
{
try
{
HttpContext.Current = ctx;
System.Threading.Thread.Sleep(5 * 60 * 1000);
Sendafter5mins(param1,params2);
}
catch(Exception e)
{
//Log Exception if any
}
});
}
catch (Exception EX)
{
//This will catch unlikely exceptions thrown from HttpContext ctx = HttpContext.Current
// or the creation of the Task
}
If by "failing silently" you mean there's an exception and you don't catch it that's because you are starting a new task without waiting for the result. Your try-catch can't catch the exception as it's stored inside the task and isn't rethrown.
Anyways, if all you want is a delay use Task.Delay with async-await instead of creating a new Task and blocking its thread:
async Task SendAfterDelay()
{
try
{
await Task.Delay(TimeSpan.FromMinutes(5));
Sendafter5mins(param1,params2);
}
catch (Exception e)
{
// handle exception
}
}

Catching exceptions in async code called synchronously

I have thrift service for authentication. catch (AccountNotFoundException) doesn't catch the exception unless I call it in Task.Run. The strange thing is that test case is fine. Why? Is it because task.start() is on the different level than catch?
public override string GetUserNameByEmail(string email)
{
var task = client.GetUserByEmail(email, false);
return task.Result;
// I changed to
// return Task.Run(() => client.GetUserByEmail(email, false)).Result.UserName;
// and I was able to catch the exception
}
public async Task<AccountDetails> GetAccountDetailsByEmail(string email)
{
try
{
return await Call(() => client.getAccountDetailsByEmail(email));
}
catch (AccountNotFoundException)
{
return null;
}
}
private async Task<T> Call<T>(Func<T> call)
{
try
{
transport.Open();
var thriftTask = new Task<T>(call);
thriftTask.Start();
return await thriftTask;
}
catch (DatabaseException e)
{
Logger.Error(e);
throw;
}
finally
{
transport.Close();
}
}
Test case works just fine
[TestMethod]
public async Task Nonexisting_User_I_Expect_To_Be_Null()
{
var user = Provider.GetUser("idontexist#bar.com", false);
Assert.IsNull(user);
}
EDIT:
I have a following theory why my code run ok: The code was working because I was lucky. Request and async was handled by the same thread so it shared the same context so it didn't block.
First, you shouldn't be calling asynchronous methods synchronously. As I describe on my blog, the approach you're using is prone to deadlocks.
The reason you're seeing an unexpected exception type is because Result will wrap any task exceptions in an AggregateException. To avoid this, you can call GetAwaiter().GetResult().
This doesn't have anything to do with Start, but since you mention it, the Start member doesn't really have a use case. There's never a good reason to use it. Instead, use Task.Run:
var thriftTask = Task.Run(call);
See here for details of exception handling for async code. It may be that you're catching an AccountNotFoundException, when you really want to be catching an Exception, which will have an InnerException set to the AccountNotFoundException:
https://msdn.microsoft.com/en-us/library/0yd65esw.aspx
An excerpt:
The task's IsFaulted property is set to True, the task's Exception.InnerException property is set to the exception, and the exception is caught in the catch block.
public async Task DoSomethingAsync()
{
Task<string> theTask = DelayAsync();
try
{
string result = await theTask;
Debug.WriteLine("Result: " + result);
}
catch (Exception ex)
{
Debug.WriteLine("Exception Message: " + ex.Message);
}
Debug.WriteLine("Task IsCanceled: " + theTask.IsCanceled);
Debug.WriteLine("Task IsFaulted: " + theTask.IsFaulted);
if (theTask.Exception != null)
{
Debug.WriteLine("Task Exception Message: "
+ theTask.Exception.Message);
Debug.WriteLine("Task Inner Exception Message: "
+ theTask.Exception.InnerException.Message);
}
}
private async Task<string> DelayAsync()
{
await Task.Delay(100);
// Uncomment each of the following lines to
// demonstrate exception handling.
//throw new OperationCanceledException("canceled");
//throw new Exception("Something happened.");
return "Done";
}

Handle exception thrown by a task

I have a task running a long time operation in WPF:
Task t = Task.Factory.StartNew(() =>
{
try
{
process(cancelTokenSource.Token, CompressionMethod, OpInfo);
}
catch (OperationCanceledException)
{
logger.Info("Operation cancelled by the user");
}
}, cancelTokenSource.Token);
try
{
t.Wait();
}
catch (AggregateException ae)
{
int i = 0;
}
private void process(CancellationToken token, CompressionLevel level, OperationInfo info)
{
// check hash
if (ComputeHash)
{
logger.Info("HASH CHECKING NOT IMPLEMENTED YET!");
MessageBox.Show(this,"HASH CHECKING NOT IMPLEMENTED YET!", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
}
token.ThrowIfCancellationRequested();
UserMsgPhase = "Operation finished";
return info;
}
Problem is "MessageBox.Show" throws an exception and it is not captured within "catch (AggregateException ae)". I've been reading about TPL exception handling but I don't understand why it is not catched. Please, could you help me?
Once the task is complete you can check its Exception property. You also have Status and IsCompleted properties which may be useful to you...
Check Task.Exception.
If your task is typed (returning a result), then accessing myTask.Result will throw this exception.
Moreover, if you are running .Net 4.5, you could use async/await.
As an example:
public async void MyButton_OnClick(object sender, EventArgs e)
{
try
{
Task t = ...your task...;
var myResult = await t; // do whatever you like with your task's result (if any)
}catch
{
// whatever you need
}
}
as you would do with synchronous code (but this is not an actual synchronous call)
I believe that the question's process method is a Task, so it looks like it could be implement in a different manner:
You can make the process to be implemented as Task and then you will have a task-child within task-parent.
Then you can make use of the TaskCreationOptions.AttachedToParent option.
According to Stephen Toub, using AttachedToParent will help notify children-task exception to the parent-task catch:
any exceptions from faulted children will propagate up to the parent
Task (unless the parent Task observes those exceptions before it
completes).
Example:
I've omitted the cancellation token parts in order for it to be more simple.
Task t = Task.Factory.StartNew(() =>
{
var process = new Task(() =>
{
//Copy here the process logic.
}, TaskCreationOptions.AttachedToParent);
//*Private failure handler*.
process.start();
});
try
{
t.Wait();
}
catch (AggregateException ae)
{
//handle exceptions from process.
}
In addition, you may add a private failure handler like:
//*Private failure handler*.
var failHandler = child.ContinueWith(t =>
{
//Oops, something went wrong...
}, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnFaulted);

Categories