将回调包装到异步任务中

时间:2019-09-11 10:35:21

标签: c# async-await

我无法控制API中的以下方法。

public void Start(Action OnReady);

总的来说,我对回调很满意,但有时您会触发回调等等。因此,我想将其包装到异步方法中,也许还包括取消操作的可能性。像这样:

await Start(cancellationToken);

这是我想出的:

public Task Start(CancellationToken cancellationToken)
{
     return Task.Run(() =>
     {
           cancellationToken.ThrowIfCancellationRequested();

           var readyWait = new AutoResetEvent(false);

           cancellationToken.Register(() => readyWait?.Set());

           Start(() => { readyWait.Set(); }); //this is the API method

           readyWait.WaitOne();
           readyWait.Dispose();
           readyWait = null;

           if(cancellationToken.IsCancellationRequested)
           {
                APIAbort(); //stop the API Start method from continuing
                cancellationToken.ThrowIfCancellationRequested();
            }

      }, cancellationToken);
}

我认为仍有改进的余地,但我想到的一件事是,在这种情况下,这种方法是什么?

readyWait.WaitOne(); 

我想编写一个异步方法以不阻塞任何线程,但这正是WaitOne所做的。当然,它不会因为任务而阻塞调用线程,但是任务是否拥有自己的线程?如果只阻止任务,那很好,但是我不想阻止其他地方可能正在使用的线程。

3 个答案:

答案 0 :(得分:3)

public Task StartAsync(CancellationToken c)
{
    var cs = new TaskCompletionSource<bool>();

    c.Register(() => { Abort(); cs.SetCanceled(); } );
    Start(() => { cs.SetResult(true); });

    return cs.Task;
}
using (var ct = new CancellationTokenSource(1000))
{
    try
    {
        await StartAsync(ct.Token);

        MessageBox.Show("Completed");
    }
    catch (TaskCanceledException)
    {
        MessageBox.Show("Cancelled");
    }
}

没有必要使用取消令牌,因为它可以触发的唯一点是在调用方法之后立即完成之前,这是竞争条件。

答案 1 :(得分:0)

这个问题出奇的棘手。案例很多,每个案例的处理并不总是很明显。

显而易见:

  1. Start方法可能需要很长时间才能完成。 APIAbort方法也是如此。
  2. 可以在CancellationToken方法之前,之中或之后甚至在Start回调调用之后取消OnReady
  3. APIAbort回调调用之前或期间或之后,不应调用Start
  4. 即使取消操作是在完成OnReady方法之前发生的,也应调用APIAbort

不明显:

  1. 返回的Start是否应在Task调用之前或之后被取消?
  2. 如果APIAbort引发异常怎么办?如果返回的APIAbort已被取消,如何传播此异常?
  3. 如果Task抛出Start异常怎么办?这是错误还是取消?

以下实现在调用OperationCanceledException方法之前先取消Task,抑制在APIAbort期间可能发生的异常,并处理 APIAbort期间OperationCanceledException被取消。

Start

设置选项public Task StartAsync(CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) return Task.FromCanceled(cancellationToken); var tcs = new TaskCompletionSource<bool>(); var cancellationRegistration = cancellationToken.Register(() => tcs.TrySetCanceled(cancellationToken)); var fireAndForget = Task.Run(() => { if (cancellationToken.IsCancellationRequested) return; try { Start(() => { cancellationRegistration.Dispose(); // Unregister tcs.TrySetResult(true); }); } catch (OperationCanceledException) { tcs.TrySetCanceled(); return; } catch (Exception ex) { tcs.TrySetException(ex); return; } // At this point Start is completed succesfully. Calling APIAbort is allowed. var continuation = tcs.Task.ContinueWith(_ => { try { APIAbort(); } catch { } // Suppressed }, default, TaskContinuationOptions.OnlyOnCanceled | TaskContinuationOptions.RunContinuationsAsynchronously, TaskScheduler.Default); }, cancellationToken); return tcs.Task; } 的原因是为了避免与TaskContinuationOptions.RunContinuationsAsynchronously之后的代码同步(在同一线程中)运行APIAbort的可能性。最初我使用await StartAsync()-async作为延续机制时遇到了this problem

答案 2 :(得分:0)

  

我想将其包装到异步方法中,也许还可以取消操作。

我建议将它们分开。原因是您的“取消”实际上并未取消expired_at。它只取消Start wait 。因此,在此级别取消会产生误导。

您可以使用与pattern for wrapping an event into a Task类似的方法将委托回调包装到Start中:

Task

现在,您可以致电public static Task StartAsync(this ApiObject self) { var tcs = new TaskCompletionSource<object>(); self.Start(() => tcs.SetResult(null)); return tcs.Task; } 并取回StartAsync,如果需要,您可以选择不继续等待:

Task