IO 异步方法中的 TaskCompletionSource 用法

TaskCompletionSource usage in IO Async methods

System.Data.SqlClient.SqlCommandExecuteNonQueryAsync()方法的实现如下:

    public override Task<int> ExecuteNonQueryAsync(CancellationToken cancellationToken) {

        Bid.CorrelationTrace("<sc.SqlCommand.ExecuteNonQueryAsync|API|Correlation> ObjectID%d#, ActivityID %ls\n", ObjectID);
        SqlConnection.ExecutePermission.Demand();   

        TaskCompletionSource<int> source = new TaskCompletionSource<int>();

        CancellationTokenRegistration registration = new CancellationTokenRegistration();
        if (cancellationToken.CanBeCanceled) {
            if (cancellationToken.IsCancellationRequested) {
                source.SetCanceled();
                return source.Task;
            }
            registration = cancellationToken.Register(CancelIgnoreFailure);
        }

        Task<int> returnedTask = source.Task;
        try {
            RegisterForConnectionCloseNotification(ref returnedTask);

            Task<int>.Factory.FromAsync(BeginExecuteNonQueryAsync, EndExecuteNonQueryAsync, null).ContinueWith((t) => {
                registration.Dispose();
                if (t.IsFaulted) {
                    Exception e = t.Exception.InnerException;
                    source.SetException(e);
                }
                else {
                    if (t.IsCanceled) {
                        source.SetCanceled();
                    }
                    else {
                        source.SetResult(t.Result);
                    }
                }
            }, TaskScheduler.Default);
        } 
        catch (Exception e) {
            source.SetException(e);
        }

        return returnedTask;
    }

我总结为:

  1. Create TaskCompletionSource<int> source = new TaskCompletionSource<int>();
  2. Create a new task using Task<int>.Factory.FromAsync, using the APM "Begin/End" API
  3. Invoke source.SetResult() when the task finishes.
  4. Return source.Task

这里使用 TaskCompletionSource 有什么意义,为什么不直接 return 由 Task<int>.Factory.FromAsync() 创建的任务?此任务还包装了结果和异常(如果有)。

在 C# in a Nutshell 书中,在 Asynchronous Programming and Continuations 部分,它指出:

In writing Delay, we used TaskCompletionSource, which is a standard way to implement “bottom-level” I/O-bound asynchronous methods.

For compute-bound methods, we use Task.Run to initiate thread-bound concurrency. Simply by returning the task to the caller, we create an asynchronous method.

为什么可以使用 Task.Run() 实现计算绑定方法,而不是 I/O 绑定方法?

请注意,要获得明确的答案,您必须询问代码的作者。除此之外,我们只能推测。但是,我认为以合理的准确性做出一些推断是合理的……

What is the point of using TaskCompletionSource here and why not to return the task created by Task.Factory.FromAsync() directly?

在这种情况下,在我看来,主要原因是允许实现在任务实际完成之前注销已注册的回调 CancelIgnoreFailure()。这确保在客户端代码收到完成通知时,API 本身已完全从操作中清除。

第二个原因可能只是为了提供完整的抽象。 IE。不允许 any 的底层实现从方法 "leak",以 Task 对象的形式,调用者可能会检查或(更糟地)在其中操作一种干扰任务正确可靠运行的方式。

Why is it that the compute-bound methods can be implemented using Task.Run(), but not the I/O bound methods?

可以 使用 Task.Run() 实现 I/O 绑定操作,但为什么呢?这样做会将一个线程提交给该操作,对于一个不需要线程的操作来说,这是一种浪费。

I/O 绑定操作通常有 I/O 完成端口和 IOCP 线程池(其中的线程处理任意数量的 IOCP 的完成)的支持,因此效率更高简单地使用现有的异步 I/O API,而不是使用 Task.Run() 调用同步 I/O 方法。