是否可以在 C# 中创建自己的非阻塞异步任务

mar*_*v12 6 c# asynchronous async-await

C# 中的许多内置 IO 函数都是非阻塞的,也就是说,它们在等待操作完成时不会保留线程。

例如System.IO.File.ReadAllLinesAsync返回 aTask<string[]>是非阻塞的。

它不只是暂停正在使用的线程,实际上还释放了该线程,以便其他进程可以使用它。

我认为这是通过调用操作系统来实现的,操作系统在检索文件时回调程序,而程序不必浪费线程等待它。

是否可以自己创建一个非阻塞异步任务?

显然,做类似的事情Thread.sleep()不会像那样释放当前线程System.IO.File.ReadAllLinesAsync。

我意识到休眠线程不会占用 CPU 资源,但它仍然占用一个线程,这在处理大量请求的 Web 服务器中可能会出现问题。

我不是在谈论一般如何生成任务。我正在谈论用于处理文件/网络调用的内置 C# 函数如何在等待时释放线程。

Lol*_*lop 1

评论中似乎对此有相当多的讨论,但我不确定他们中是否有人按照你想要的方式回答它,所以我会尽力而为。

目前,我通常可以通过两种方式来调用没有任务的异步方法。这些通常是旧的 API(例如SqlCommand.BeginExecuteNonQuery),已被基于任务的调用替换。如果您有更具体的场景,那么提供更好的示例将很有帮助。

我正在谈论用于处理文件/网络调用的内置 C# 函数如何在等待时释放线程。

您问这个,但您已经说过“我认为这是通过以操作系统回调程序的方式调用操作系统来完成的”。你有点回答了你自己的问题。这些内置操作正在进行的调用会移交给操作系统,并在完成后收到操作系统的警报。

在我的示例中,假设CallFoo正在调用某种处理所有事务的操作系统操作。操作系统调用方式的实际实现对于您来说并不是非常重要,您无需担心,但如果您想了解更多信息,可以研究从 C# 调用 Windows 内核。

异步与回调

想象一下这个函数看起来像这样:

public void CallFoo(Action finishedCallback);
Run Code Online (Sandbox Code Playgroud)

你希望能够做到这一点,这样你就可以这样称呼它:

public Task CallFoo();
Run Code Online (Sandbox Code Playgroud)

我会这样定义它:

public Task CallFoo()
{
    var taskCompletionSource = new TaskCompletionSource();

    // Calls to the API that has a non blocking IO call but no async Task API
    CallFoo(() =>
    {
        // Callback is called when the IO task has finished.
        // SetResult will mark the returned Task as complete
        taskCompletionSource.SetResult();
    });

    return taskCompletionSource.Task;
}
Run Code Online (Sandbox Code Playgroud)

与句柄异步

我认为它工作的另一种方式是使用某种返回的“句柄”,指定异步任务是否已完成。

该方法可能看起来像这样:

public IAsyncHandle CallFoo();
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我会像这样实现它:

public async Task CallFoo()
{
    var handle = CallFoo();

    while (!handle.IsCompleted)
    {
        await Task.Delay(100);
    }
}
Run Code Online (Sandbox Code Playgroud)

这不太理想,因为您只是轮询以查看它是否完成,但它确实比执行 thread.sleep 使用的资源少得多。明显的缺点是它并没有真正对异步操作完成做出实时反应。您可以根据需要减少/增加延迟。