Tengo una clase heredada que me gustaría consumir de una manera más conveniente.
class Camera { void RequestCapture(); public delegate void ReadResultEventHandler(ControllerProxy sender, ReadResult readResult); public event ReadResultEventHandler OnReadResult; }La clase maneja una cámara que toma fotografías. Funciona por:
La operación es asíncrona. RequestCapture es una operación de disparo y olvido (¡rápida!). Después de unos segundos, el evento se levantará.
Me gustaría consumirlo como una Task regular o como un IObservable<ReadResult> pero estoy bastante perdido, ya que no se adapta al Patrón Async, y nunca he usado una clase como esta.
Siguiendo el ejemplo de este artículo, mi enfoque rudimentario (¡no probado!) sería:
class AsyncCamera { private readonly Camera camera; public AsyncCamera(Camera camera) { this.camera = camera ?? throw new ArgumentNullException(nameof(camera)); } public Task<ReadResult> CaptureAsync() { TaskCompletionSource<ReadResult> tcs = new TaskCompletionSource<ReadResult>(); camera.OnReadResult += (sender, result) => { tcs.TrySetResult(result); }; camera.RequestCapture(); return tcs.Task; } }Posible uso:
public async Task DoSomethingAsync() { var asyncCam = new AsyncCamera(new Camera()); var readResult = await asyncCam.CaptureAsync(); // use readResult }Entonces, esta es mi opinión ingenua sobre la cancelación de la suscripción al controlador de eventos. No tuve tiempo de hacer ninguna prueba, por lo que se aceptan sugerencias para mejorar.
class AsyncCamera { private readonly Camera camera; public AsyncCamera(Camera camera) { this.camera = camera ?? throw new ArgumentNullException(nameof(camera)); } public Task<ReadResult> CaptureAsync() { ReadResultEventHandler handler = null; TaskCompletionSource<ReadResult> tcs = new TaskCompletionSource<ReadResult>(); handler = (sender, result) => { camera.OnReadResult -= handler; tcs.TrySetResult(result); }; camera.OnReadResult += handler; camera.RequestCapture(); return tcs.Task; } }En caso de que los comentarios se "limpien": Stephen dijo
La cancelación de la suscripción no se maneja en los ejemplos de MS, pero realmente debería ser así. Tendría que declarar una variable de tipo ReadResultDelegate (o como se llame), establecerla en nulo y luego establecerla en la expresión lambda que luego puede cancelar la suscripción de esa variable del evento. No tengo un ejemplo de esto en mi blog, pero hay uno de propósito general aquí . Lo cual, ahora que lo miro, no parece manejar la cancelación de manera adecuada. – Stephen Cleary
Énfasis por mí.
Parece funcionar: https://dotnetfiddle.net/9XsaUB
Podría convertir el evento Camera.OnReadResult en una secuencia observable usando el operador FromEvent :
var camera = new Camera(); IObservable<ReadResult> observable = Observable .FromEvent<Camera.ReadResultEventHandler, ReadResult>( h => (sender, readResult) => h(readResult), h => camera.OnReadResult += h, h => camera.OnReadResult -= h); Alternativamente, podría usar el operador Create . Es más detallado, pero probablemente también más fácil de comprender.
IObservable<ReadResult> observable = Observable.Create<ReadResult>(o => { camera.OnReadResult += Camera_OnReadResult; void Camera_OnReadResult(ControllerProxy sender, ReadResult readResult) { o.OnNext(readResult); } return Disposable.Create(() => camera.OnReadResult -= Camera_OnReadResult); });