Exceptions are just ignored in async code block - c#

Before I use Nito.MVVM, I used plain async/await and it was throwing me an aggregate exception and I could read into it and know what I have. But since Nito, my exceptions are ignored and the program jumps from async code block and continue executes. I know that it catch exceptions because when I put a breakpoint on catch(Exception ex) line it breaks here but with ex = null. I know that NotifyTask has properties to check if an exception was thrown but where I put it, it checks when Task is uncompleted, not when I need it.
View model:
public FileExplorerPageViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
_manager = new FileExplorerManager();
Files = NotifyTask.Create(GetFilesAsync("UniorDev", "GitRemote/GitRemote"));
}
Private method:
private async Task<ObservableCollection<FileExplorerModel>> GetFilesAsync(string login, string reposName)
{
return new ObservableCollection<FileExplorerModel>(await _manager.GetFilesAsync(login, reposName));
}
Manager method(where exception throws):
public async Task<List<FileExplorerModel>> GetFilesAsync(string login, string reposName)
{
//try
//{
var gitHubFiles = await GetGitHubFilesAsync(login, reposName);
var gitRemoteFiles = new List<FileExplorerModel>();
foreach ( var file in gitHubFiles )
{
if ( file.Type == ContentType.Symlink || file.Type == ContentType.Submodule ) continue;
var model = new FileExplorerModel
{
Name = file.Name,
FileType = file.Type.ToString()
};
if ( model.IsFolder )
{
var nextFiles = await GetGitHubFilesAsync(login, reposName);
var count = nextFiles.Count;
}
model.FileSize = file.Size.ToString();
gitRemoteFiles.Add(model);
}
return gitRemoteFiles;
//}
//catch ( WebException ex )
//{
// throw new Exception("Something wrong with internet connection, try to On Internet " + ex.Message);
//}
//catch ( Exception ex )
//{
// throw new Exception("Getting ExplorerFiles from github failed! " + ex.Message);
//}
}
With try/catch or without it has the same effect. This behavior is anywhere where I have NotifyTask.
Update
There is no event, that fires when exception occurred, but there is Property Changed event, so I used it and added this code:
private void FilesOnPropertyChanged(object sender, PropertyChangedEventArgs propertyChangedEventArgs)
{
throw new Exception("EXCEPTION");
bool failed;
if ( Files.IsFaulted )
failed = true;
}
And exception not fires.
I added throw exception in App class (main class) and it fired. And when I have exceptions that come from XAML, it also fires. So maybe it not fires when it comes from a view model, or something else. I have no idea. Will be very happy for some help with it.
Update
We deal with exception = null, but the question is still alive. What I wanna add, that I rarely this issue, when the app is starting to launch on the physic device. I read some info about it, and it doesn't seem to be related, but maybe it is:

I'm not entirely sure what your desired behavior is, but here's some information I hope you find useful.
NotifyTask is a data-bindable wrapper around Task. That's really all it does. So, if its Task faults with an exception, then it will update its own data-bindable properties regarding that exception. NotifyTask is intended for use when you want the UI to respond to a task completing, e.g., show a spinner while the task is in progress, an error message if the task faults, and data if the task completes successfully.
If you want your application to respond to the task faulting (with code, not just a UI update), then you should use try/catch like you have commented out in GetFilesAsync. NotifyTask doesn't change how those exceptions work; they should work just fine.
I know that it catch exceptions because when I put a breakpoint on catch(Exception ex) line it breaks here but with ex = null.
That's not possible. I suggest you try it again.
I know that NotifyTask has properties to check if an exception was thrown but where I put it, it checks when Task is uncompleted, not when I need it.
If you really want to (asynchronously) wait for the task to complete and then check for exceptions, then you can do so like this:
await Files.TaskCompleted;
var ex = Files.InnerException;
Or, if you just want to re-raise the exception:
await Files.Task;
Though I must say this usage is extremely unusual. The much more proper thing to do is to have a try/catch within your GetFilesAsync.

Related

Exception in loaded plugin crashes parent application

I'm writing a .NET 6 application for which users can create plugins. In some situations however, when a plugin throws an unhandled exception, my own application crashes as well. That should not happen, no matter what. The plugin may stop working, it may unload, it may whatever, just leave the parent app alive.
Loading happens like this:
public static ServiceInfo? LoadService(string relativePath)
{
var loadContext = new ServiceLoadContext(relativePath);
_alcs.Add(loadContext);
try
{
var assembly = loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(relativePath)));
var shouldLoadDll = false;
foreach (var type in assembly.GetTypes())
{
if (typeof(IMonitorableService).IsAssignableFrom(type))
{
var directoryName = new FileInfo(relativePath).Directory!.FullName;
if (Activator.CreateInstance(type, new object[] { directoryName }) is IMonitorableService result)
{
shouldLoadDll = true;
return new ServiceInfo
{
Category = Directory.GetParent(relativePath)!.Name,
Name = Path.GetFileNameWithoutExtension(relativePath),
AssemblyPath = relativePath,
Service = result!
};
}
}
}
if (!shouldLoadDll)
{
loadContext.Unload();
}
}
catch (Exception)
{
// This is handled, but this won't catch the exception in the plugin
}
return null;
}
I have my share of try/catch phrases, and since these IMonitorableServices are BackgroundServices, they're started like
public async Task StartAsync(CancellationToken cancellationToken)
{
foreach (var service in _options.Services)
{
try
{
await service.Service.StartAsync(cancellationToken);
}
catch (Exception ex)
{
// This is handled, but it won't catch the exception in the plugin
}
}
}
Now I doubt that it's really relevant to provide the specific error, but just in case: it's a
'System.InvalidOperationException: 'Collection was modified; enumeration operation may not execute',
following an operation on event subscriptions. I know how to solve that in the plugin, but I could never trust my future plugin writers to always handle their exceptions (or prevent them from happening). I need some way to catch absolutely everything in my own application. I've been breaking my head over this and I can find many considerations on plugins loaded in AppDomains, but they're from the .NET Framework era...
Who has an idea how to solve this? I could hardly imagine this is something that has been overlooked in .NET Core/6 development.
Update: I find that other type of exceptions actually are caught within the StartAsync method. So it might have something to do with the exception being raised from an event in the plugin (don't want to put you on the wrong track though). I must add, the event is registered from within the StartAsync method, but it seems to bypass the regular catch.

throw original exception inside Parallel methods instead of aggregate exception

I have two CPU-intensive methods inside a Parallel.Invoke call:
Parallel.Invoke(
() => { GetMaxRateDict(tradeOffObj); },
() => { GetMinRateDict(tradeOffObj); }
);
For a MCVE, assume:
public void GetMaxRateDict(object junk)
{
throw new Exception("Max exception raised, do foo...");
}
public void GetMinRateDict(object moreJunk)
{
throw new Exception("Min exception raised, do bar...")
}
I throw different exceptions in each of these inner methods. However, if one of these gets thrown, the Parallel wrapper throws a more generic exception: "One or more errors occurred", which is specific enough to show in my UI layer.
Can I grab the original exception somehow and throw it instead?
I would like the Parallel task to stop entirely if possible to raise the inner exception, but if that's not possible, at least being able to raise it once the two methods complete is what I need. Thanks.
Can I grab the original exception somehow and throw it instead?
"It" implies that there will only be on exception. Even though that's probably true, because you're executing actions in parallel you can't 100% rule out the possibility that multiple actions throw exceptions even if you attempt to cancel the others after the first exception. If you're okay with that, we can go from the assumption that we only expect one exception and we're okay with only catching one. (If you allow the other invocation to continue after one throws an exception the possibility of having two exceptions increases.)
You can use a cancellation token. If one of the invocations below throws an exception, it should catch that exception, place it in a variable or queue, and then call
source.Cancel;
Doing so will cause the entire Parallel.Invoke to throw an OperationCanceledException. You can catch that exception, retrieve the exception that was set, and rethrow that.
I'm going to go with the other answer's suggestion of a ConcurrentQueue just as a matter of practice because I don't think we can rule out the remote possibility that a second thread could throw an exception before being canceled.
This started off seeming small, but eventually it got so involved that I separated it into its own class. This makes me question whether my approach is needlessly complex. The main intent was to keep the messy cancellation logic from polluting your GetMaxRateDict and GetMinRateDict methods.
In addition to keeping your original methods unpolluted and testable, this class is itself testable.
I suppose I'll find out from the other responses whether this is a decent approach or there's something much simpler. I can't say I'm particularly excited about this solution. I just thought it was interesting and wanted to write something that did what you asked.
public class ParallelInvokesMultipleInvocationsAndThrowsOneException //names are hard
{
public void InvokeActions(params Action[] actions)
{
using (CancellationTokenSource source = new CancellationTokenSource())
{
// The invocations can put their exceptions here.
var exceptions = new ConcurrentQueue<Exception>();
var wrappedActions = actions
.Select(action => new Action(() =>
InvokeAndCancelOthersOnException(action, source, exceptions)))
.ToArray();
try
{
Parallel.Invoke(new ParallelOptions{CancellationToken = source.Token},
wrappedActions)
}
// if any of the invocations throw an exception,
// the parallel invocation will get canceled and
// throw an OperationCanceledException;
catch (OperationCanceledException ex)
{
Exception invocationException;
if (exceptions.TryDequeue(out invocationException))
{
//rethrow however you wish.
throw new Exception(ex.Message, invocationException);
}
// You shouldn't reach this point, but if you do, throw something else.
// In the unlikely but possible event that you get more
// than one exception, you'll lose all but one.
}
}
}
private void InvokeAndCancelOthersOnException(Action action,
CancellationTokenSource cancellationTokenSource,
ConcurrentQueue<Exception> exceptions)
{
// Try to invoke the action. If it throws an exception,
// capture the exception and then cancel the entire Parallel.Invoke.
try
{
action.Invoke();
}
catch (Exception ex)
{
exceptions.Enqueue(ex);
cancellationTokenSource.Cancel();
}
}
}
The usage would then be
var thingThatInvokes = new ParallelInvokesMultipleInvocationsAndThrowsOneException();
thingThatInvokes.InvokeActions(
()=> GetMaxRateDict(tradeOffObj),
() => GetMinRateDict(tradeOffObj));
If it throws an exception, it will be a single exception from one invocation failure, not an aggregate exception.
Not quite sure whether given example would answer your question, but it might improve overall solution:
private static void ProcessDataInParallel(byte[] data)
{
// use ConcurrentQueue to enable safe enqueueing from multiple threads.
var exceptions = new ConcurrentQueue<Exception>();
// execute the complete loop and capture all exceptions
Parallel.ForEach(data, d =>
{
try
{
// something that might fail goes here...
}
// accumulate stuff, be patient ;)
catch (Exception e) { exceptions.Enqueue(e); }
});
// check whether something failed?..
if (exceptions.Count > 0) // do whatever you like ;
}
Such an approach gives additional freedom in terms of collecting different kinds of exceptions into different queues (if necessary) or re-throwing aggregated exception further (such that no sensitive info bubbled up or you may convey particular exception with a user-friendly description of possible reasons, etc.).
Generally, that is correct way of exception management with parallelization. Not only in C#.

VS2017 doesn't give details for an exception, just crashed with null

I'm working on a UWP project and there's something funky going on with how errors are being presented to me. I don't know if it's VS2017 or how UWP is set up.
I have a piece of code that goes online and retrieves json content, sometimes the code works and sometimes it doesn't. It works when I use Expander control from UWP Community toolkit, and fails when I want to switch to GridView. When it doesn't work, it fails on GetStringAsync method of HttpClient. The strange behavior is that the exception isn't thrown in the method where the problem occurs, the code actually redirects me back without giving an error and as soon as it gets to the property that's supposed to have a value that isn't null, I get a null exception.
This is where the problem happens:
string httpContent = "";
using (HttpClient httpClient = new HttpClient())
{
try
{
httpContent = await httpClient.GetStringAsync(uri);
}
catch (Exception e)
{
// TODO: handle errors
var x = "";
}
}
This piece of code is called from within the view model. It starts with a constructor and RefreshServerKanesWrathDataAsync is the method where json is parsed.
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
cnconline.RefreshServerKanesWrathDataAsync();
}
The second I get to GetStringAsync, the code just goes back to the constructor like nothing happened, however the method never completes, it just exits back to the constructor, and therefore fails to update observable collections with data. I then get a null exception.
I wanted to test this with VS2015, but I updated some controls that are apparently only supported withing VS2017, so I can't run the code in other versions.
I also ran into an issue with the code prior to this problem, where I tried to access files in a directory without using a token. The behavior was exactly the same, the code wasn't telling me that I didn't have access to the directory I wanted to read, it was just throwing me out of the method back into the location that made the call to read the directory. Just like with the current problem, I would then run into a null exception, which wasn't where the main problem was.
I added Template10 and UWP community toolkit to the project, if that matters.
You shouldn't call an async method from a constructor unless you're willing to provide a callback.
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
var t = cnconline.RefreshServerKanesWrathDataAsync(); // assuming returns Task<string>
t.ContinueWith(OnCompleted);
}
private void OnCompleted(Task<string> task)
{
if (task.IsFaulted)
{
// Check error
var exception = task.Exception;
}
else if (task.IsCanceled)
{
// User hit cancel?
}
else
{
// All good!
var result = task.Result;
}
}
Here's a sample where RefreshServerKanesWrathDataAsync() returns just Task (not Task<result>)
public CncOnlinePageViewModel()
{
cnconline = new CncOnline();
var t = cnconline.RefreshServerKanesWrathDataAsync(); // assuming returns Task
t.ContinueWith(OnCompleted);
}
private void OnCompleted(Task task)
{
if (task.IsFaulted)
{
// Check error
var exception = task.Exception;
}
else if (task.IsCanceled)
{
// User hit cancel?
}
else
{
// All good!
}
}
On a side note, you may also need to have Visual Studio 2017 break when any exception is thrown. In VS2017, go to Debug->Windows->Exception Settings and make sure Common Language Runtime Exceptions has a check. If it has a filled box, click the box until it turns into a checkmark.
Also..., you can tap into an event raised when any task has an unobserved exception. You can do so in the constructor of App.xaml.cs
public App()
{
TaskScheduler.UnobservedTaskException += OnUnobservedException;
}
private static void OnUnobservedException(object sender, UnobservedTaskExceptionEventArgs e)
{
// Put break point here.
var ex = e.Exception;
// This will keep your app alive, but only do it if it's safe to continue.
e.SetObserved();
}

FileNotFoundException, but catch block is not entered (Async method)

I have a method in which I'm checking if a file exists in my filesystem (UWP):
private async Task<bool> FileExistsAsync(string filename)
{
try
{
if (filename != null)
{
var item = await StorageFile.GetFileFromPathAsync(filename); // FileNotFoundException happens here
if (item != null)
return true;
}
return false;
}
catch (Exception e) // I Expect the debugger to handle the FileNotFoundException here, but it never happens
{
return false;
}
}
When I'm trying to get a non-existing file with StorageFile.GetFileFromPathAsync, I get an exception. For some reason it's not handled by my catch block, but looks like this instead:
What I've tried:
Explicitly handle a FileNotFoundException
Add a Try-catch-block inside of the if-statement
Note that the method needs to stay asynchronous because I have other stuff going on in this method which I removed in order to provide a Minimal, Complete, and Verifiable example.
Why is the debugger not entering my catch block when a FileNotFoundException is thrown?
In the Exception Popup i see [x] Break when this exception type is thrown. Try disabling it there, or from the Exception Settings dialog (Ctrl-Alt-E on my system, or menu Debug > Windows > Exception Settings).
var item = await StorageFile.GetFileFromPathAsync(filename);
Did you have try and catch in this function ?? if yes, add
throw
in catch
try this :Why can't I catch an exception from async code?
and this : Catch an exception thrown by an async method can help you.

Getting a Task's unexpected Exception to bring down the application earlier than Garbage Collection

Typically, for code that I don't expect to throw exceptions but does (i.e. a programming error), I want my application to crash (so that it doesn't corrupt data, report invalid data to the user, etc.).
Is there a best practice for getting (closer to) this behavior when using Tasks? We've registered a handler for TaskScheduler.UnobservedTaskException. The problem is that this can occur much later than the causing unexpected exception.
Question:
Which option should I use if any:
Should I wrap my Tasks action in a try/catch and escalate in the catch for exceptions I don't expect? And if so, what should I do to escalate (i.e. I'd like to get it to fire the AppDomain.UnhandledException event and terminate.
Should I attach a continuation (OnlyOnFaulted) on the ui thread (this is a Winforms application) that rethrows the exception if it is not an expected exception?
Is there a better or more standard approach?
Here's what #1 might look like:
var t1 = Task.Factory.StartNew(() =>
{
try
{
string path = null; // Programming error. Should have been a valid string. Will cause System.ArgumentNullException below
using (FileStream fs = File.Create(path))
{
}
}
catch (System.IO.IOException) { throw; } // Expected possible exception
catch (System.UnauthorizedAccessException) { throw; }
catch
{
// Anything caught here is not an expected exception and should be escalated.
// But how?
}
});
Here's what #2 might look like:
TaskScheduler uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
var t1 = Task.Factory.StartNew(() =>
{
string path = null; // Programming error. Should have been a valid string. Will cause System.ArgumentNullException below
using (FileStream fs = File.Create(path))
{
}
});
t1.ContinueWith(t =>
{
Exception ex = t.Exception;
if (ex is IOException || ex is UnauthorizedAccessException) // Expected exceptions (do nothing)
return;
throw ex; // Not expected (escalate by rethrowing)
}, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, uiTaskScheduler);
Attaching a continuation feels like a good approach to me. If you're comfortable with the assumption that you won't be blocking the UI thread for too long for other reasons, forcing the continuation to run on the UI thread seems like a very reasonable option to me. That way you can perform any UI tasks you need to as well, as part of the emergency shutdown.

Categories

Resources