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

我是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,遵循Jeffrey Richter的this article,然后在我的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.

真正的异步是不可能的,因为您必须使用阻塞方法.所以,你所能做的只是一个解决方法假异步,a.k.a.阻塞线程池线程.最简单的方法是:

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,这意味着真正的异步.

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

相关文章

目录简介使用JS互操作使用ClipLazor库创建项目使用方法简单测...
目录简介快速入门安装 NuGet 包实体类User数据库类DbFactory...
本文实现一个简单的配置类,原理比较简单,适用于一些小型项...
C#中Description特性主要用于枚举和属性,方法比较简单,记录...
[TOC] # 原理简介 本文参考[C#/WPF/WinForm/程序实现软件开机...
目录简介获取 HTML 文档解析 HTML 文档测试补充:使用 CSS 选...