从遗留 class(启动方法 + 事件)创建任务(或 Observable)

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

我有一份遗产class,我想以更方便的方式使用它。

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

class 管理一个拍照的相机。它的工作原理是:

  1. 调用 RequestCapture 方法
  2. 等待引发 OnReadResult(显然,您需要订阅此事件才能获取捕获的数据)

操作是异步的。 RequestCapture 是一种即发即弃操作(快速!)操作。几秒钟后,事件将引发。

我想像常规 TaskIObservable<ReadResult> 一样使用它,但我很迷茫,因为它不适应异步模式,而且我从来没有像这样使用 class。

按照 this article 中的示例,我的基本方法(未经测试!!)将是:

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

可能的用法:

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

考虑到 Stephen 的评论进行编辑(谢谢你!)

所以,这是我取消订阅事件处理程序的幼稚想法。我没有时间做任何测试,所以欢迎提出改进建议。

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

以防评论被“清理”:斯蒂芬说

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

我强调的。

似乎有效:https://dotnetfiddle.net/9XsaUB

您可以使用 FromEvent 运算符将 Camera.OnReadResult 事件转换为可观察序列:

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

或者您可以使用 Create 运算符。它更冗长,但也可能更容易理解。

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