简体   繁体   中英

Create a Task (or Observable) from legacy class (start method + events)

I have a legacy class which I would like to consume in a more convenient way.

class Camera 
{
     void RequestCapture();
     public delegate void ReadResultEventHandler(ControllerProxy sender, ReadResult readResult);
     public event ReadResultEventHandler OnReadResult;
}

The class manages a camera that takes pictures. It works by:

  1. Invoking the RequestCapture method
  2. Awaiting for the OnReadResult to be raised (obviously, you need to subscribe to this event in order to get the captured data)

The operation is asynchronous. The RequestCapture is a fire-and-forget operation (fast.) operation, After some seconds. the event will raise.

I would like to consume it either like as regular Task or as an IObservable<ReadResult> but I'm quite lost, since it doesn't adapt to the Async Pattern, and I have never used a class like this.

Following the example in this article my rudimentary approach (untested:!) would be:

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;
    }
}

Possible usage:

public async Task DoSomethingAsync()
{
    var asyncCam = new AsyncCamera(new Camera());
    var readResult = await asyncCam.CaptureAsync();
    // use readResult
}

EDIT taking Stephen's comment into account (THANK you!)

So, this is my naive take on unsubscribing the event handler. I didn't have the time to make any tests, so suggestions for improvement are welcome.

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;
    }
}

In case comments get "cleaned up": Stephen said

Unsubscription isn't handled in the MS examples, but it really should be. You'd have to declare a variable of type ReadResultDelegate (or whatever it's called), set it to null, and then set it to the lambda expression which can then unsubscribe that variable from the event. I don't have an example of this on my blog, but there's a general-purpose one here . Which, now that I look at it, does not seem to handle cancellation appropriately. Stephen Cleary

Emphasis by me.

Seems to work: https://dotnetfiddle.net/9XsaUB

You could convert the Camera.OnReadResult event to an observable sequence using the FromEvent operator:

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);

Alternatively you could use the Create operator. It's more verbose, but also probably easier to comprehend.

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);
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM