Created
April 30, 2021 23:11
-
-
Save kameko/61478e79d80302b678ca8b261c122dac to your computer and use it in GitHub Desktop.
ExceptionListener
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
namespace Your.Namespace.Here | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Threading; | |
using System.Threading.Tasks; | |
public static class ExceptionListenerExtensions | |
{ | |
public static Task TellIfTaskFaulted(this Task task, ExceptionListener listener) | |
{ | |
listener.TellTaskFault(task); | |
return task; | |
} | |
public static Task ContinueAndTellIfTaskFaulted(this Task task, ExceptionListener listener) | |
{ | |
return task.ContinueWith(cont => listener.TellTaskFault(cont)); | |
} | |
} | |
/// <summary> | |
/// Allows the main thread of the program to let other threads inform it | |
/// that an unhandled exception was encountered in the system, allowing | |
/// the main thread to inspect the exception and safely shut down. | |
/// </summary> | |
public sealed class ExceptionListener : IDisposable | |
{ | |
/// <summary> | |
/// The amount of exceptions stored in the system. | |
/// </summary> | |
public int ExceptionCount => exceptions.Count; | |
/// <summary> | |
/// The CancellationToken that is canceled if an exception is thrown. | |
/// </summary> | |
public CancellationToken CancellationToken => listen_cts.Token; | |
/// <summary> | |
/// Raised when the system is informed of an exception. The "Wait" methods | |
/// will not finish blocking until this event has finished executing. | |
/// If this event, ironically, throws an unhandlex exception, then that | |
/// exception will be added to the AggregateException and this event will | |
/// not be raised again. | |
/// </summary> | |
public event Action<Exception, ExceptionListener> OnExceptionRaised; | |
/// <summary> | |
/// Raised when a warning is mentioned to the system. | |
/// </summary> | |
public event Action<Exception> OnWarn; | |
private CancellationTokenSource listen_cts; | |
private List<Exception> exceptions; | |
/// <summary> | |
/// Allows the main thread of the program to let other threads inform it | |
/// that an unhandled exception was encountered in the system, allowing | |
/// the main thread to inspect the exception and safely shut down. | |
/// </summary> | |
public ExceptionListener() | |
{ | |
OnExceptionRaised += delegate { }; | |
OnWarn += delegate { }; | |
listen_cts = new CancellationTokenSource(); | |
exceptions = new List<Exception>(); | |
} | |
/// <summary> | |
/// Reset the listener, clearing all exceptions stored. | |
/// </summary> | |
public void Reset() | |
{ | |
listen_cts.Cancel(); | |
listen_cts = new CancellationTokenSource(); | |
exceptions.Clear(); | |
} | |
/// <summary> | |
/// Inform the listener of a warning event. | |
/// </summary> | |
/// <param name="e"></param> | |
public void Warn(Exception e) | |
{ | |
try | |
{ | |
OnWarn.Invoke(e); | |
} | |
catch (Exception ei) | |
{ | |
Tell(ei); | |
} | |
} | |
/// <summary> | |
/// Tell the listener that an unhandled exception was encountered. | |
/// </summary> | |
/// <param name="e"></param> | |
public void Tell(Exception e) | |
{ | |
exceptions.Add(e); | |
InvokeOnExceptionRaised(e); | |
listen_cts.Cancel(); | |
} | |
/// <summary> | |
/// Checks if the provided Task has faulted, and if so, tells the system. | |
/// </summary> | |
/// <param name="task"></param> | |
public void TellTaskFault(Task task) | |
{ | |
if (task.IsFaulted) | |
{ | |
Tell(task.Exception!); | |
} | |
} | |
/// <summary> | |
/// Get all stored exceptions, if any. | |
/// </summary> | |
/// <returns></returns> | |
public AggregateException? GetExceptions() | |
{ | |
if (exceptions.Count > 0) | |
{ | |
return new AggregateException(exceptions); | |
} | |
return null; | |
} | |
/// <summary> | |
/// Throw an AggregateException storing all stored exceptions. | |
/// If no exceptions are stored, this does not throw. | |
/// </summary> | |
public void ThrowException() | |
{ | |
if (exceptions.Count > 0) | |
{ | |
throw new AggregateException(exceptions).Flatten(); | |
} | |
} | |
/// <summary> | |
/// Get the first exception stored. | |
/// </summary> | |
/// <returns></returns> | |
public Exception? GetFirstException() | |
{ | |
return exceptions.FirstOrDefault(); | |
} | |
/// <summary> | |
/// Get the last exception stored. | |
/// </summary> | |
/// <returns></returns> | |
public Exception? GetLastException() | |
{ | |
return exceptions.LastOrDefault(); | |
} | |
/// <summary> | |
/// Block this thread until an unhandled exception is thrown. This does not throw. | |
/// </summary> | |
/// <param name="other_token"></param> | |
public void Wait(CancellationToken other_token = default) | |
{ | |
var token = CancellationTokenSource.CreateLinkedTokenSource(listen_cts.Token, other_token).Token; | |
while (!token.IsCancellationRequested) | |
{ | |
Thread.Sleep(16); | |
} | |
} | |
/// <summary> | |
/// Asynchronously wait until an unhandled exception is thrown. This does not throw. | |
/// </summary> | |
/// <param name="other_token"></param> | |
/// <returns></returns> | |
public async Task WaitAsync(CancellationToken other_token = default) | |
{ | |
try | |
{ | |
var token = CancellationTokenSource.CreateLinkedTokenSource(listen_cts.Token, other_token).Token; | |
await Task.Delay(-1, token); | |
} | |
catch (TaskCanceledException) | |
{ | |
// ignore | |
} | |
} | |
/// <summary> | |
/// Block this thread until an unhandled exception is thrown, then re-throw all | |
/// exceptions as an AggregateException, if any are stored. | |
/// </summary> | |
/// <param name="other_token"></param> | |
public void WaitAndThrow(CancellationToken other_token = default) | |
{ | |
Wait(other_token); | |
ThrowException(); | |
} | |
/// <summary> | |
/// Asynchronously wait until an unhandled exception is thrown, then re-throw all | |
/// exceptions as an AggregateException, if any are stored. | |
/// </summary> | |
/// <param name="other_token"></param> | |
/// <returns></returns> | |
public async Task WaitAndThrowAsync(CancellationToken other_token = default) | |
{ | |
await WaitAsync(other_token); | |
ThrowException(); | |
} | |
public void Dispose() | |
{ | |
listen_cts.Cancel(); | |
} | |
private void InvokeOnExceptionRaised(Exception e) | |
{ | |
try | |
{ | |
OnExceptionRaised.Invoke(e, this); | |
} | |
catch (Exception ee) | |
{ | |
exceptions.Add(ee); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment