如果我知道 API 在某个时候执行 I/O,我是否应该异步调用棕地 API?

Should I make a call to a brownfield API asynchronous if I know the API does I/O at some point?

我正在开发一个项目,该项目使用我们编写的旧 API(即不是第三方,但目前未在开发中)。旧的 API 执行非托管 I/O 操作:它通过运行时可调用包装器 (RCW) 使用 COM DLL 连接到中间层服务器。这个新项目本身将成为一个新应用程序的 API,正如我所说,它利用了旧的 API;但是,我想在适当的时候公开异步方法,因为我还不确定我将如何使用新的 API。例如,如果我在 Web 应用程序中使用它,我想确保我不会阻塞不必要地等待 COM DLL 深处发生的 I/O 操作的线程。

这是我的新应用程序的样子:

------------------------------
|  CLIENT (WIN32, WEB, ETC.)  |
-------------------------------
------------------------------
|         NEW API             |
-------------------------------
------------------------------
|         OLD API             |
-------------------------------
------------------------------
|         COM DLL             |
-------------------------------

这真的很简单,看起来新的 API 的唯一目的是包装旧的 API,但新的 API 是一个单独的应用程序,它具有它自己的业务逻辑,并使用旧的 API 进行一定比例的操作。

我已经阅读了很多关于 async/await 的适当使用的内容,尤其是在棕地应用程序中; Stephen Cleary's MSDN Magazine article on the topic for example is very helpful. I'm struggling in my case, though. If I'm not directly making use of a truly-asynchronous I/O method, e.g. HttpClient.GetAsync, but I know (or am reasonably sure) the COM DLL is doing I/O without a thread, is there still no thread?换句话说,线程是否在通过旧同步 API 调用的 COM DLL 内部深处遇到 I/O 操作后立即放弃?

这是一些示例代码。这是旧的 API:

public class OldApi
{
    public bool TryCheckOutDocument(int docNum, out Document document)
    {
        // use COM dll to "check out" the doc.
    }

    public bool TryCheckInDocument(Document document)
    {
        // " " "check in" the doc.
    }
}

public class Document
{
    public int DocNum { get; }
    public object OtherData { get; set; }
}

这是新的 API:

public class NewApi
{
    public async Task ConvertDocsAsync(IEnumerable<int> docNums)
    {
        var oldApi = new OldApi();

        Parallel.ForEach(docNums, async (docNum) =>
        {
            Document doc = null;

            if (await Task.Run(() => !oldApi.TryCheckOutDocument(docNum, out doc)))
                throw new Exception($"blah blah: {docNum}");

            doc.OtherData = "this represents the conversion";

            if (await Task.Run(() => !oldApi.TryCheckInDocument(doc)))
                throw new Exception($"blah blah: {docNum}");
        });
    }
}

我正在使用 Task.Run 调用旧 API 中的同步方法。当他们命中 I/O ops 时,线程会被放弃吗?如果没有,是否有更好的方法使用 async/await 来确保最有效地使用异步?

I'm using Task.Run to call the synchronous methods in the old API. When they hit I/O ops, will the thread be relinquished? If not, is there a better way to use async/await to ensure the most-efficient use of asynchrony?

没有;恐怕旧的 API 是同步的,你不能 "force" 它是异步的。

If I'm not directly making use of a truly-asynchronous I/O method... but I know (or am reasonably sure) the COM DLL is doing I/O without a thread, is there still no thread? In other words, does the thread get relinquished as soon as it hits the I/O operation deep down inside the COM DLL called via the old synchronous API?

所有 I/O 本质上都是异步的,但在这种情况下,第一个同步调用会阻塞 I/O 上的线程。因此,即使 COM DLL 是异步的,旧的 API 也只会公开同步的 APIs - 旧的 API 会阻塞线程。

附带说明一下,将 asyncParallel 一起使用肯定会导致痛苦的经历。

你现在最好的选择就是保持同步:

Parallel.ForEach(docNums, docNum =>
{
  Document doc = null;

  if (!oldApi.TryCheckOutDocument(docNum, out doc))
    throw new Exception($"blah blah: {docNum}");

  doc.OtherData = "this represents the conversion";

  if (!oldApi.TryCheckInDocument(doc))
    throw new Exception($"blah blah: {docNum}");
});

直到旧的API更新为异步方法,此时可以做异步并发:

var tasks = docNums.Select(async docNum =>
{
  Document doc = await oldApi.CheckOutDocumentAsync(docNum);
  doc.OtherData = "this represents the conversion";
  await oldApi.CheckInDocumentAsync(doc);
});
await Task.WhenAll(tasks);