Vor einiger Zeit hat Ralph Westphal das Konzept des RequestWithResponsePins vorgestellt. Da geht es um Event-Basierte Programmierung und dem Problem, dass das Abschicken einer Frage aus Methode A durch Events als Antwort in einer Methode B zurück kommt und damit die Geschäftslogik stückelt.
Um die Antwort in der gleichen Methode zu bekommen, in der die Frage gestellt worden ist, hat er die Klasse RequestWithResponsePin vorgestellt:
using System;
namespace RequestWithResponsePin
{
public class RequestWithResponsePin<TRequest, TResponse>
{
public RequestWithResponsePin(TRequest data, Action<TResponse> responsePin)
{
Data = data;
ResponsePin = responsePin;
}
public TRequest Data { get; private set; }
public Action<TResponse> ResponsePin { get; private set; }
}
public static class RequestExtensions
{
public static void Request<TRequest, TResponse>(this Action<RequestWithResponsePin<TRequest, TResponse>> outputPin, TRequest data, Action<TResponse> responsePin)
{
outputPin(new RequestWithResponsePin<TRequest, TResponse>(data, responsePin));
}
}
}
Das ermöglicht weiterhin Event-basiert zu arbeiten, á la Event Based Components (EBC), aber die Logik zu konzentrieren.
Das Verbinden der Events mit Handlern kann über ebc.componentbinder dann sogar automatisch folgen, usw.
Hier ein Beispiel (Event und Handler in einer Klasse, würde man auf verschiedene Komponenten aufteilen):
public class EBCDemo
{
public event Action<RequestWithResponsePin<string, bool>> OnSomething;
public void ProcessSomething(RequestWithResponsePin<string, bool> req )
{
var str = req.Data; // mach was
// Antwort
req.ResponsePin(true);
}
public void Start()
{
var result = false;
// Event feuern und Antwort bekommen
OnSomething.Request("mein input", output => result = output );
Console.WriteLine("Antwort {0}", result);
}
}
Und so wird es benutzt:
[STAThread]
static void Main()
{
var ebcDemo = new EBCDemo();
// Bind
ebcDemo.OnSomething += ebcDemo.ProcessSomething;
ebcDemo.Start();
}
Eine recht feine Sache, welche die Abhängigkeiten der einzelnen Komponenten lösen kann.
Mein Problem ist, wie kann ich dieses Konzept in die Welt von async und await bringen?
Der Vorteil von async/await ist, dass ich ähnlich wie beim RequestWithResponsePin die Logik zusammen halten kann. Hier nur auf Threads bezogen, die länger dauern.
Ich muss also nicht auf nen BackgroundWorker.RunWorkerCompleted warten, und dort den Rest der Geschäftslogik fortsetzen.
Wenn ich also im obrigen Beispiel in ProcessSomething ein Thread.Sleep(3000) mache, so friert die Ausführung ein. In der Konsole merkt man dies nicht, in der Gui schon.
Allerdings bekomme ich es nicht hin, den RequestWithResponsePin async fähig zu machen.
Entweder es geht nicht, oder ich habe zu wenig Erfahrung mit async (was wahrscheinlicher ist 😃 )
Damit das ganze async funktioniert müsste meines Verständnisses nach das bool in OnSomething und ProcessSomething als Task<bool> verpackt werden.
Die ProcessSomething würde dann so aussehen müssen:
public void ProcessSomething(RequestWithResponsePin<string, bool> req )
{
var str = req.Data;
var res = await Task.Run(
() =>
{
Thread.Sleep(3000);
return true;
}
);
req.ResponsePin(res);
}
Geht natürlich nicht, weil die Methode async modifier braucht (kein Problem) aber auch einen Task returnen muss.
Da aber der Return-Value dieser Methode nicht benutzt wird, sondern quasi zur nächsten Action über req.ResponsePin(true); propagiert wird, bekomme ich keine Asynchronizität dadurch.
Ferner habe ich das Gefühl, als müsste das bool im req in ein Task gewrappt werden, was dann aber über req.ResponsePin(res); nicht mehr zurückgegeben werden kann.