We nemen een Button waar je events aan kan hangen (aan Click) die gevuurd worden als je er op klikt (door DoClick aan te roepen).
Nu wil ik dat de Exceptions die eventHandlers gooien de boel niet laten crashen. Ik heb daarom het volgende bedacht:
Je stopt je functie/lambda door een statische Create-methode die er wat veiligers van maakt. Door overloading wordt automatisch de sync of async versie gepakt. Een Action is een functie die void is en een Func heeft een return-type. Dit werkt vrij aardig (in mijn console-test-project).
Probleem 1: Als een gewone/sync lamdba een Exception gooit pakt de verkeerde Create-methode hem. Hoe kan dat ineens? De rest van de lambda's gaat wel goed.
Probleem 2: Voor echte functies heb ik een cast nodig omdat de compiler anders niet weet wat hij moet doen. Is er een manier om die cast kwijt te raken? Of zit er niets anders op dan geen overloading meer te gebruiken? Ik vind het raar dat lambda's (meestal) geen problemen geven.
Als alternatief heb ik ook een veilige Button bedacht:
Probleem 3: Als ik sync dingen aan de SafeClick event hang werkt alles goed, maar bij async dingen niet. Ik kreeg geen gele lijn in mijn ide en mijn callstack zegt:
Dit werkt ook al niet (met zelfde crash):
Het probleem zijn async-functies die void terug geven. Als je een async-functie hebt die Task of Task<T> terug geeft krijg je geen crashes (de exception zie je pas bij await). Die laatsten kan ik niet in mijn event stoppen, maar dat is het probleem niet.
Hoe vang ik Exceptions op of schakel die uit bij async void? Is daar echt niets voor? Ik kwam dit tegen: http://lunarfrog.com/blog...81-async-void-exceptions/
edit:
Update bij Probleem 2. Als ik overloading niet meer gebruik geeft dit weer een niet te vangen crash (CreateSync is hernoemde Create(Action<... van boven):
Aaaaargggg... Het lukt gewoon niet mooi (= handig in gebruik en met weinig kans op fouten door gebruikers).
Een fail-safe button of handler-factory zou toch mogelijk moeten zijn? Iemand nog ideeen/tips?
[edit2]
Ik heb nu mijn eigen delegates. Dat maakt het casten wat dragelijker.
Probleem 2 is opgelost. (Al vind ik het nog steeds oneerlijk dat lambda's naar return types kijken en gewone functies niet)
C#:
1
2
3
4
5
6
7
8
9
10
| class Button { public event EventHandler Click; public void DoClick() { if (Click != null) { Click (this, null); } } } |
Nu wil ik dat de Exceptions die eventHandlers gooien de boel niet laten crashen. Ik heb daarom het volgende bedacht:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| class SafeHandler { public static EventHandler Create(Action<object, EventArgs> handler) { return (sender, e) => { try { handler (sender, e); } catch (Exception ex) { Console.WriteLine ("Sync Exception: " + ex.Message); } }; } public static EventHandler Create(Func<object, EventArgs, Task> asyncHandler) { return async (sender, e) => { try { await asyncHandler (sender, e); } catch (Exception ex) { Console.WriteLine ("Async Exception: " + ex.Message); } }; } } |
Je stopt je functie/lambda door een statische Create-methode die er wat veiligers van maakt. Door overloading wordt automatisch de sync of async versie gepakt. Een Action is een functie die void is en een Func heeft een return-type. Dit werkt vrij aardig (in mijn console-test-project).
Probleem 1: Als een gewone/sync lamdba een Exception gooit pakt de verkeerde Create-methode hem. Hoe kan dat ineens? De rest van de lambda's gaat wel goed.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // this lambda is seen as async Button btnExceptionV2 = new Button (); btnExceptionV2.Click += SafeHandler.Create ((sender, e) => { throw new Exception ("btnExceptionV2 did something wrong"); }); btnExceptionV2.DoClick (); // this one is correct Button btnExceptionV3 = new Button (); btnExceptionV3.Click += SafeHandler.Create ((sender, e) => { if (e == null) throw new Exception ("btnExceptionV3 did something wrong"); }); btnExceptionV3.DoClick (); // async works also correctly Button btnAsyncExceptionV2 = new Button (); btnAsyncExceptionV2.Click += SafeHandler.Create (async (sender, e) => { await Task.Delay (3000); throw new Exception ("btnAsyncExceptionV2 too"); }); btnAsyncExceptionV2.DoClick (); |
Probleem 2: Voor echte functies heb ik een cast nodig omdat de compiler anders niet weet wat hij moet doen. Is er een manier om die cast kwijt te raken? Of zit er niets anders op dan geen overloading meer te gebruiken? Ik vind het raar dat lambda's (meestal) geen problemen geven.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| static void NotALambda(object sender, EventArgs e) { Console.WriteLine ("Hello from NotALambda"); } static async Task AsyncNotALambda(object sender, EventArgs e) { await Task.Delay (1500); Console.WriteLine ("Hello from AsyncNotALambda"); } // not a lambda does work Button btnNotLambdaV2 = new Button (); btnNotLambdaV2.Click += SafeHandler.Create ((Action<object, EventArgs>)NotALambda); btnNotLambdaV2.DoClick (); // async not a lambda also works Button btnAsyncNotLambdaV2 = new Button (); btnAsyncNotLambdaV2.Click += SafeHandler.Create ((Func<object, EventArgs, Task>)AsyncNotALambda); btnAsyncNotLambdaV2.DoClick (); |
Als alternatief heb ik ook een veilige Button bedacht:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| class SafeButton : Button { public event EventHandler SafeClick; public SafeButton () { Click += saveClickHandler; } private void saveClickHandler(object sender, EventArgs e) { try { if (SafeClick != null) SafeClick (sender, e); } catch (Exception ex) { Console.WriteLine ("Exception: " + ex.Message); } } } |
Probleem 3: Als ik sync dingen aan de SafeClick event hang werkt alles goed, maar bij async dingen niet. Ik kreeg geen gele lijn in mijn ide en mijn callstack zegt:
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () in
System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__1 (state={System.Runtime.ExceptionServices.ExceptionDispatchInfo}) in
System.Threading.QueueUserWorkItemCallback.WaitCallback_Context (state={System.Threading.QueueUserWorkItemCallback}) in
System.Threading.ExecutionContext.RunInternal (executionContext={System.Threading.ExecutionContext}, callback={System.Threading.ContextCallback}, state={System.Threading.QueueUserWorkItemCallback}, preserveSyncCtx=true) in
System.Threading.ExecutionContext.Run (executionContext={System.Threading.ExecutionContext}, callback={System.Threading.ContextCallback}, state={System.Threading.QueueUserWorkItemCallback}, preserveSyncCtx=true) in
System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem () in
System.Threading.ThreadPoolWorkQueue.Dispatch () in
System.Threading._ThreadPoolWaitCallback.PerformWaitCallback () in
[Native to Managed Transition] in
Dit werkt ook al niet (met zelfde crash):
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
| EventHandler AsyncLambda = async (sender, e) => { await Task.Delay (3000); Console.WriteLine ("AsyncLambda"); throw new Exception ("AsyncLambda did something wrong"); }; // doesn't work try { AsyncLambda (null, null); } catch (Exception e) { Console.WriteLine ("Exception: " + e.Message); } |
Het probleem zijn async-functies die void terug geven. Als je een async-functie hebt die Task of Task<T> terug geeft krijg je geen crashes (de exception zie je pas bij await). Die laatsten kan ik niet in mijn event stoppen, maar dat is het probleem niet.
Hoe vang ik Exceptions op of schakel die uit bij async void? Is daar echt niets voor? Ik kwam dit tegen: http://lunarfrog.com/blog...81-async-void-exceptions/
edit:
Update bij Probleem 2. Als ik overloading niet meer gebruik geeft dit weer een niet te vangen crash (CreateSync is hernoemde Create(Action<... van boven):
C#:
1
2
3
4
5
6
| Button btnAsyncHandlerV3 = new Button (); btnAsyncHandlerV3.Click += SafeHandler.CreateSync (async (sender, e) => { await Task.Delay (3000); throw new Exception ("btnAsyncHandlerV3 did something wrong"); }); btnAsyncHandlerV3.DoClick (); |
Aaaaargggg... Het lukt gewoon niet mooi (= handig in gebruik en met weinig kans op fouten door gebruikers).
Een fail-safe button of handler-factory zou toch mogelijk moeten zijn? Iemand nog ideeen/tips?
[edit2]
Ik heb nu mijn eigen delegates. Dat maakt het casten wat dragelijker.
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| class SafeHandler { public delegate void HandlerType(object sender, EventArgs e); public delegate Task AsyncHandlerType(object sender, EventArgs e); public static EventHandler Create(HandlerType handler) { return (sender, e) => { // ... // not a lambda does work Button btnNotLambdaV2 = new Button (); btnNotLambdaV2.Click += SafeHandler.Create ((SafeHandler.HandlerType)NotALambda); btnNotLambdaV2.DoClick (); |
Probleem 2 is opgelost. (Al vind ik het nog steeds oneerlijk dat lambda's naar return types kijken en gewone functies niet)
[ Voor 10% gewijzigd door Daos op 23-03-2014 01:17 ]