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

标签 c# .net multithreading asynchronous async-await

我正在开发一个项目,该项目使用我们编写的旧 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例如非常有帮助。不过,我在我的情况下很挣扎。如果我不是直接使用真正异步的 I/O 方法,例如HttpClient.GetAsync ,但我知道(或相当确定)COM DLL 正在执行 I/O 时没有线程,有没有 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 操作时,线程会被放弃吗?如果没有,是否有更好的方法使用 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 是同步的,您不能“强制”它是异步的。

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 也仅公开同步 API - 旧 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);

关于c# - 如果我知道 API 在某个时候执行 I/O,我是否应该异步调用 brownfield API?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45040896/

相关文章:

c# - mvc中的无限多级菜单

java - 意外的并发修改异常

c# - 将图像切成小块

c# - 如何遍历组合框值列表并选择其中一个

c# - 如何检查给定进程已创建到 Internet 的哪些连接

c# - RangeValidator 货币值小数点后不能超过 2 位?

java - 如何让一个执行器FixedThreadPool等待另一个执行器SingleThreadExecutor?

.net - 线程未从 Thread.Sleep() 唤醒

c# - 最新的 VS Mac 更新导致 Xamarin Android 中的 SetContentView(resourceid) 出现 SIGSEGV

c# - 任意成员变量的任意计算