c# - 异步使用同步 WCF 服务

标签 c# .net wcf async-await

我目前正在将客户端应用程序迁移到 .NET 4.5 以使用 async/await。该应用程序是当前仅提供同步服务的 WCF 服务的客户端。我现在想知道,我应该如何异步使用这个同步服务

我正在使用 channel factories使用在服务器和客户端之间共享的服务契约(Contract)连接到 WCF 服务。因此,我无法使用 VisualStudio 或 svcutil 的自动生成来生成异步客户端代理。

我已阅读 this related question这是关于是否使用 Task.Run 在客户端包装同步调用,或者是否改为使用异步方法扩展服务契约。答案表明,服务器提供“真正的”异步方法对客户端性能更好,因为没有线程必须主动等待服务调用完成。这对我来说确实很有意义,这意味着同步调用应该包装在服务器端。

另一方面,Stephen Toub 在 this blog post 中一般不鼓励这样做.现在,他没有在那里提到 WCF,所以我不确定这是否只适用于在同一台机器上运行的库,或者它是否也适用于远程运行的东西,但异步性的引入对连接/传输。

毕竟,由于服务器实际上并不是异步工作的(并且很可能暂时不会),一些线程将始终需要等待:无论是在客户端还是在服务器上。这在同步使用服务时也适用(目前,客户端在后台线程上等待以保持 UI 响应)。

例子

为了更清楚地说明问题,我准备了一个例子。完整项目可用于 download here .

服务器提供同步服务GetTest。这是当前存在的,也是同步进行工作的地方。一种选择是将其包装在异步方法中,例如使用 Task.Run,并将该方法作为契约(Contract)中的附加服务提供(需要扩展契约(Contract)接口(interface))。

// currently available, synchronous service
public string GetTest() {
    Thread.Sleep(2000);
    return "foo";
}

// possible asynchronous wrapper around existing service
public Task<string> GetTestAsync() {
    return Task.Run<string>(() => this.GetTest());
}

// ideal asynchronous service; not applicable as work is done synchronously
public async Task<string> GetTestRealAsync() {
    await Task.Delay(2000);
    return "foo";
}

现在,在客户端,该服务是使用 channel 工厂创建的。这意味着我只能访问服务契约定义的方法,尤其不能访问异步服务方法,除非我明确定义和实现它们。

根据现在可用的方法,我有两个选择:

  1. 我可以通过封装调用来异步调用同步服务:

    await Task.Run<string>(() => svc.GetTest());
    
  2. 我可以直接异步调用服务端提供的asynchronous服务:

    await svc.GetTestAsync();
    

两者都工作正常,并且不会阻塞客户端。这两种方法都涉及在某个端忙等待:选项 1 在客户端等待,这相当于之前在后台线程中完成的操作。选项 2 通过在服务器上包装同步方法在服务器上等待。

使同步 WCF 服务异步感知的推荐方法是什么?我应该在哪里执行包装,在客户端还是在服务器上?或者是否有更好的选择来做到这一点而无需在任何地方等待,即通过在连接上引入“真正的”异步性——就像生成的代理一样?

最佳答案

从异步的角度来看,客户端和服务器端是完全分开的,它们根本不关心彼此。你应该在你的服务器上有你的同步功能,并且只有你的服务器上有同步功能。

如果你想“正确”地做到这一点,在客户端你将无法重用与用于生成服务器的接口(interface)相同的接口(interface)来生成你的 channel 工厂。

所以你的服务器端看起来像这样

using System.ServiceModel;
using System.Threading;

namespace WcfService
{
    [ServiceContract]
    public interface IService
    {
        [OperationContract]
        string GetTest();
    }

    public class Service1 : IService
    {
        public string GetTest()
        {
            Thread.Sleep(2000);
            return "foo";
        }
    }
}

你的客户端看起来像这样

using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace SandboxForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            var button = new Button();
            this.Controls.Add(button);

            button.Click += button_Click;
        }

        private async void button_Click(object sender, EventArgs e)
        {
            var factory = new ChannelFactory<IService>("SandboxForm.IService"); //Configured in app.config
            IService proxy = factory.CreateChannel();

            string result = await proxy.GetTestAsync();

            MessageBox.Show(result);
        }
    }

    [ServiceContract]
    public interface IService
    {
        [OperationContract(Action = "http://tempuri.org/IService/GetTest", ReplyAction = "http://tempuri.org/IService/GetTestResponse")]
        Task<string> GetTestAsync();
    }
}

关于c# - 异步使用同步 WCF 服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22638828/

相关文章:

c# - 如何以编程方式获取所有 .NET 程序集的列表?

c# - 长整数文字

c# - 通过网络将 C# SecureString 发送到字节 - 我应该担心编码吗?

c# - 从 IClientMessageInspector 登录时屏蔽敏感信息

c# - 如何将未定义数量的类型参数传递给泛型方法?

C#屏幕截图错误?

c# - 如何联合两个 List<T> 并将结果分配给第三个 List<T>?

.net - 无法添加对 dll 的引用

c# - 编译器提示缺少命名空间,该命名空间是类的名称

c# - 如何使用 AForge 库提取特定帧