当内部调用不是自然异步时,如何创建自然异步方法?

时间:2014-07-11 18:39:01

标签: c# .net asynchronous async-await dotnet-httpclient

在这种情况下,系统A需要向系统B发送消息。以下代码显示了如何完成此操作的示例:

public interface IExecutionStrategy
{
    Task<Result> ExecuteMessage(Message message);
}

public class WcfExecutionStrategy
{
    public async Task<Result> ExecuteMessage(Message message)
    {
        using (var client = new Client())
        {
            return await client.RunMessageOnServer(message);
        }
    }
}

public class MessageExecutor
{
    private readonly IExecutionStrategy _strategy;

    public MessageExecutor(IExecutionStrategy strategy)
    {
        _strategy = strategy;
    }

    public Task<Result> ExecuteMessage(Message msg)
    {
        // ....
        // Do some common checks and code here
        // ....

        var result = await _strategy.ExecuteMessage(msg);

        // ....
        // Do some common cleanup and logging here
        // .....

        return result;
    }
}

由于超出此问题范围的原因,我们决定从Wcf切换到使用原始http流,但我们需要并排收集指标并对其进行测试。所以我创建了一个新的IExecutionStrategy实现来处理这个问题:

public class HttpclientExecutionStrategy
{
    public async Task<Result> ExecuteMessage(Message message)
    {
        var request = CreateWebRequestmessage
        var responseStream = await Task.Run(() =>
        {
            var webResponse = (HttpWebResponse)webRequest.GetResponse();
            return webResponse.GetResponseStream();
        }

        return MessageStreamer.ReadResultFromStream(responseStream);
    }
}

基本上,我可以将其设置为异步的唯一方法是将其包装在Task.Run()中,以便Web请求无阻塞。 (注意:由于发送和接收的唯一流操作要求,实现这一点并不是直接在HttpClient,即使可能这个事实超出此范围问题)。

我们认为这很好,直到我们在库代码和Asp.Net应用程序中阅读Stephen Cleary's multiple blog posts关于Task.Run()如何不好的问题。这对我来说非常有意义。

如果第三方库不支持异步移动,那么实际上如何实现自然异步调用是没有意义的。例如,如果您使用HttpClient.GetStreamAsync()那么这样做会使异步操作比Task.Run(() => HttpClient.GetStream())更好,并且有没有办法为非异步第三方库解决这个问题?

2 个答案:

答案 0 :(得分:9)

HttpClient.GetStreamAsync是一个纯异步方法,这意味着在进行调用时不会引入新线程,并且在与await结合使用时,会将控制权交还给调用者,直到IO请求为止已经完成了。这将很好地扩展,因为您实际上释放调用操作的ThreadPool线程在执行请求时执行更多工作,因此您的服务器实际上可以在此期间处理更多请求。

相反,使用专用线程(同步异步)只是为了阻止IO请求调用肯定不会很好地扩展,如果执行时间足够长,最终可能会导致饥饿。

修改

XXXAsync实现的真正异步性质来自向OS提供异步端点的网络设备驱动程序。在幕后,WinHTTP(感谢@Noseratio用于更正)库用于异步操作。这意味着生成I / O请求包(IRP)并将其传递给设备驱动程序。请求完成后,将发生CPU中断,最终导致调用的回调被调用。您可以查看这些示例:Using WinInet HTTP functions in Full Asynchronous ModeWindows with C++ - Asynchronous WinHTTP以获取异步示例,当然还可以阅读Stephan Cleary的优秀There Is No Thread。您可以自己实现它并将其包装在托管包装中。

答案 1 :(得分:6)

简答

如果操作是真正异步的,请使用async-await。如果不是,那就不要假装它。

长答案

异步操作只有两个真正的好处:可伸缩性卸载。可伸缩性主要用于服务器端,而卸载用于&#34;特殊&#34; 线程(主要是GUI线程)。

如果您依赖同步库并且无法做任何事情,那么使用Task.Run使其异步并不能提高可扩展性 >(实际上可能会阻碍它),而你只能卸下。 async仅在操作本质上是异步时,在整个实际异步部分(网络,磁盘等)中there is no thread时减少资源使用

如果您正在开发富客户端应用程序,请继续使用&#34;同步异步&#34;与async-await。但对于任何其他类型的应用程序,没有理由使用async-await,我建议不要使用它。