2019-08-16 08:51:35 +00:00
using System ;
2021-03-16 09:54:15 +00:00
using System.Threading ;
2019-08-16 08:51:35 +00:00
using System.Threading.Tasks ;
using Tapeti ;
namespace ExampleLib
{
/// <summary>
/// Callback method for ExampleConsoleApp.Run
/// </summary>
/// <param name="dependencyResolver">A reference to the dependency resolver passed to the ExampleConsoleApp</param>
/// <param name="waitForDone">Await this function to wait for the Done signal</param>
public delegate Task AsyncFunc ( IDependencyResolver dependencyResolver , Func < Task > waitForDone ) ;
/// <summary>
/// Since the examples do not run as a service, we need to know when the example has run
/// to completion. This helper injects IExampleState into the container which
/// can be used to signal that it has finished. It also provides the Wait
/// method to wait for this signal.
/// </summary>
public class ExampleConsoleApp
{
private readonly IDependencyContainer dependencyResolver ;
2021-03-16 09:54:15 +00:00
private readonly int expectedDoneCount ;
2021-05-29 19:51:58 +00:00
private int doneCount ;
2022-11-17 15:47:07 +00:00
private readonly TaskCompletionSource < bool > doneSignal = new ( ) ;
2019-08-16 08:51:35 +00:00
2019-08-16 09:47:57 +00:00
/// <param name="dependencyResolver">Uses Tapeti's IDependencyContainer interface so you can easily switch an example to your favourite IoC container</param>
2021-05-29 19:51:58 +00:00
/// <param name="expectedDoneCount"></param>
2021-03-16 09:54:15 +00:00
public ExampleConsoleApp ( IDependencyContainer dependencyResolver , int expectedDoneCount = 1 )
2019-08-16 08:51:35 +00:00
{
this . dependencyResolver = dependencyResolver ;
2021-03-16 09:54:15 +00:00
this . expectedDoneCount = expectedDoneCount ;
2019-08-16 08:51:35 +00:00
dependencyResolver . RegisterDefault < IExampleState > ( ( ) = > new ExampleState ( this ) ) ;
}
/// <summary>
/// Runs the specified async method and waits for completion. Handles exceptions and waiting
/// for user input when the example application finishes.
/// </summary>
/// <param name="asyncFunc"></param>
public void Run ( AsyncFunc asyncFunc )
{
try
{
asyncFunc ( dependencyResolver , WaitAsync ) . Wait ( ) ;
}
catch ( Exception e )
{
Console . WriteLine ( UnwrapException ( e ) ) ;
}
finally
{
Console . WriteLine ( "Press any Enter key to continue..." ) ;
Console . ReadLine ( ) ;
}
}
/// <summary>
/// Returns a Task which completed when IExampleState.Done is called
/// </summary>
public async Task WaitAsync ( )
{
await doneSignal . Task ;
2019-08-20 09:47:53 +00:00
// This is a hack, because the signal is often given in a message handler before the message can be
// acknowledged, causing it to be put back on the queue because the connection is closed.
// This short delay allows consumers to finish. This is not an issue in a proper service application.
await Task . Delay ( 500 ) ;
2019-08-16 08:51:35 +00:00
}
internal Exception UnwrapException ( Exception e )
{
while ( true )
{
2022-11-22 12:20:47 +00:00
if ( e is not AggregateException aggregateException )
2019-08-16 08:51:35 +00:00
return e ;
if ( aggregateException . InnerExceptions . Count ! = 1 )
return e ;
e = aggregateException . InnerExceptions [ 0 ] ;
}
}
internal void Done ( )
{
2021-03-16 09:54:15 +00:00
if ( Interlocked . Increment ( ref doneCount ) = = expectedDoneCount )
doneSignal . TrySetResult ( true ) ;
2019-08-16 08:51:35 +00:00
}
private class ExampleState : IExampleState
{
private readonly ExampleConsoleApp owner ;
public ExampleState ( ExampleConsoleApp owner )
{
this . owner = owner ;
}
public void Done ( )
{
owner . Done ( ) ;
}
}
}
}