c# - 是否可以等待未声明为异步的 IO 操作?如果没有,我该怎么办?

标签 c# multithreading asynchronous task

我是 C# 异步编程的新手,我仍然对一些事情感到困惑。 我读到在 .NET 4.5 之后,不再建议将 APM 和 EAP 用于新开发,因为 TAP 应该取代它们 ( source )。

我想我了解 async/await 的工作原理,并且我能够使用它们来执行具有异步方法的 IO 操作。例如,我可以编写等待 HttpWebClient 的 GetStringAsync 结果的异步方法,因为它被声明为异步方法。太好了。

我的问题是:如果我们有一个 IO 操作发生在未声明为异步的方法中怎么办?像这样:假设我有一个 API,它有一个方法

string GetResultFromWeb()

从 Web 查询内容。我有很多不同的查询要做,而且我必须使用这种方法来完成。然后我需要处理每个查询结果。我知道如果那是一个异步方法我会这样做:

Task<string> getResultTask = GetResultFromWeb(myUrl); 
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

但由于它不是,我不能等待它——它告诉我字符串不可等待。所以我的问题是:是否有任何方法可以异步执行这些 IO 操作,而不必为每个查询创建一个线程?如果可以的话,我希望创建尽可能少的线程,而不必阻塞任何线程。

我发现这样做的一种方法是实现 APM,遵循 this article来自 Jeffrey Richter,然后在我的 Begin 方法中调用 ThreadPool.QueueWorkItem(GetResultFromWeb, asyncResult)。像这样:

public class A {
    private void DoQuery(Object ar){
        AsyncResult<string> asyncResult = (AsyncResult<string>) ar;
        string result = GetResultFromWeb();
        asyncResult.SetAsCompleted(result, false);
    }

    public IAsyncResult BeginQuery(AsyncCallback){
        AsyncResult<string> asyncResult = new AsyncResult<string>(callback, this);
        ThreadPool.QueueUserWorkItem(DoQuery, asyncResult);
        return asyncResult;
    }

    public string EndQuery(IAsyncResult ar){
        AsyncResult<string> asyncResult = (AsyncResult<string>)ar;
        return asyncResult.EndInvoke();
    }
}

然后我使用 AsyncEnumerator 并开始 (BeginQuery) 多个查询并在每个查询完成时处理结果(使用 yield return/EndQuery)。这似乎运作良好。但是在阅读了这么多 APM 已过时之后,我想知道如何使用 TAP 来做到这一点。另外,这种APM方法有什么问题吗?

谢谢!

最佳答案

what if we have an IO operation that happens in a method that is not declared as async?

在这种情况下,I/O 操作是阻塞的。换句话说,GetResultFromWeb 阻塞调用线程。在我们完成其余部分时请记住这一点......

I must use this method to do so.

据此我推断您不能编写异步的 GetResultFromWebAsync 方法。因此,任何执行 Web 请求的线程都必须被阻止。

is there any way of performing these IO operations asynchronously without having to create one thread for each query?

最自然的方法是编写一个 GetResultFromWebAsync 方法。由于这是不可能的,您的选择是:阻止调用线程,或阻止其他线程(即线程池线程)。阻塞线程池线程是一种我称之为“假异步”的技术 - 因为它看起来是异步的(即,不阻塞 UI 线程)但实际上不是(即,它只是阻塞线程池线程)。

If I could, I'd like to create as less threads as possible, without having to block any of the threads.

鉴于限制,这是不可能的。如果您必须使用GetResultFromWeb 方法,并且该方法阻塞调用线程,则线程必须被阻塞.

One way I found to do so was by implementing APM, following this article from Jeffrey Richter and then, in my Begin method, I call ThreadPool.QueueWorkItem(GetResultFromWeb, asyncResult).

在这种情况下,您的代码公开了一个异步 API(开始/结束),但在实现中它只是在线程池线程上调用 GetResultFromWeb。即,它是伪异步。

This seems to work well.

它可以工作,但不是真正的异步。

But after reading so much that APM is obsolete, I was wondering how could I do this using TAP.

正如其他人所指出的,有一种更简单的方法可以将工作安排到线程池:Task.Run

真正的异步是不可能的,因为您必须使用阻塞 方法。因此,您所能做的就是解决方法 - 假异步,也就是阻塞线程池线程。最简单的方法是:

Task<string> getResultTask = Task.Run(() => GetResultFromWeb(myUrl)); 
// Do whatever I need to do that doesn't need the query result
string result = await getResultTask;
Process(result);

(比 APM 和 AsyncEnumerator 更简洁的代码)

请注意,我建议创建使用假异步实现的 GetResultFromWebAsync 方法。返回任务的异步后缀方法应该遵循 Task-based Asynchronous Pattern guidelines ,这意味着 true 异步。

换句话说,正如我在我的博客 use Task.Run to invoke a method, not to implement a method 中更详细地描述的那样.

关于c# - 是否可以等待未声明为异步的 IO 操作?如果没有,我该怎么办?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27790468/

相关文章:

java - run() 方法后线程继续运行

python - 在主任务中显示来自后台任务的计数变量(Tkinter GUI)

Javascript 按钮 OnClick 回调

c# - 复制 Excel 图表并将其移动到另一个工作表

c# - 如何以编程方式启用\禁用 ToolStripMenuItem 中的嵌套子菜单项?

c# - 匿名方法最短语法

c - 最快的多线程排序方法

c# - @html.Dropdown 和推特 Bootstrap

validation - Angular2 模板驱动的异步验证器

c# - 启动和即发即弃异步调用的正确方法?