实现异步 WCF 服务

Posted

技术标签:

【中文标题】实现异步 WCF 服务【英文标题】:Implementing an async WCF service 【发布时间】:2010-12-25 19:54:31 【问题描述】:

我有一个 WPF 应用程序,我目前正在努力将其分离为客户端和服务器端 - 使用 WCF。我不喜欢我最初使用直截了当的解决方案时遇到的混乱,所以现在我正在按照 Miguel Castro 的截屏视频WCF Extreme 中的建议进行重组。如果您不熟悉视频,他基本上是手动设置整个通信 - 不使用服务参考。这包括:

包含所有服务和数据合同的通用合同 - 由客户端和服务器引用 托管服务的控制台应用程序 客户端上的代理类映射服务,并将调用传递给它(使用 ClientBase 或 ClientFactory)

我已经按照他的所有步骤进行操作,我真的很喜欢他的发展方向。但是,他没有处理异步服务调用,而这正是我想要使用的。

添加服务引用时,我可以选中“生成异步操作”复选框,并获得 MyServiceCompleted 和 MyServiceAsync。但是,我想这是在添加服务引用时生成的东西,而不是它所构建的类中的一些魔法?

那么,我能以某种方式从 ClientBase 或 ClientFactory 获得异步操作吗?还是我必须将实际的服务器端服务定义为异步的?如果是这样 - 有人可以给我一些关于如何开始使用简单异步服务的提示或示例吗?我一直在 MSDN 上阅读有关该主题的信息,但这让我感到很困惑,感觉自己像个白痴,因为还没有明白这一点..

【问题讨论】:

【参考方案1】:

在服务器端实现异步操作非常简单。确保您的方法名称匹配并以 Begin 和 End 为前缀。 GetImageAsyncResult 是一个自定义的 IAsyncResult 实现(网络上有很多示例)。

    public class MapProvider : IMapProvider //implementation - belongs to server
    
         public IAsyncResult BeginGetImage(int level, int x, int y, string[] layers, AsyncCallback callback, object state)
         
              GetImageAsyncResult asyncResult = new GetImageAsyncResult(level, x, y, layers, callback, state);
              ThreadPool.QueueUserWorkItem(Callback, asyncResult);
              return asyncResult;
         

         private void Callback(object state)
         

              GetImageAsyncResult asyncResult = state as GetImageAsyncResult;
              asyncResult.Image = TileProvider.GetImage(asyncResult.Level, asyncResult.X, asyncResult.Y, asyncResult.Layers);
              asyncResult.Complete();
         

         public System.Drawing.Bitmap EndGetImage(IAsyncResult result)
         
              using (GetImageAsyncResult asyncResult = result as GetImageAsyncResult)
              
                   asyncResult.AsyncWait.WaitOne();
                   return asyncResult.Image;
              
         
    

    public class MapProviderProxy : ClientBase<IMapProvider>, IMapProvider, IDisposable
    
         public IAsyncResult BeginGetImage(int level, int x, int y, string[] layers, AsyncCallback callback, object state)
         
              return Channel.BeginGetImage(level, x, y, layers, callback, state);
         

         public System.Drawing.Bitmap EndGetImage(IAsyncResult result)
         
              return Channel.EndGetImage(result);
         

         public void Dispose()
         
              if (State == CommunicationState.Faulted)
              
                   Abort();
              
              else
              
                   try
                   
                        Close();
                   
                   catch
                   
                        Abort();
                   
              
         
    

【讨论】:

谢谢! IMapProvider 将定义 BeginGetImage 和 EndGetImage 函数 - 标记为“[OperationContract(AsyncPattern = true)]”?我需要从中定义具体的结果,GetImageAsyncResult? 正确 - 我在网上找到了一个基本的 AsyncResult 类。 GetImageAsyncResult 就是从中派生出来的。 好的 - 这回答了我的问题。但是,我意识到我不想让服务异步,但我想异步调用同步服务。 IE。我将在客户端处理它。我使用 AsyncMethodCaller 轻松解决了这个问题。还是谢谢!【参考方案2】:

当您选择“生成异步操作”时,正如您所指出的,不涉及任何魔法:Begin... 将开始您的通信,服务器开始处理内容,但在您调用 End... 之前您不能使用任何东西。

所有这些行为都发生在客户端,因此您无需在服务实现中进行任何更改。

您可能认为这一定很复杂,但事实并非如此;)

编辑:这里举个例子:

using (Service.SampleClient client = new Service.SampleClient())

    client.AddCompleted += 
        (object sender, Service.AddCompletedEventArgs e)
            
                Console.WriteLine(e.Result); // 2
            ;
    client.AddAsync(1, 1);

    // wait for async callback
    Console.ReadLine();


[ServiceContract]
public interface ISample

    [OperationContract]
    int Add(int a, int b);

您还可以明确地编程您的服务以异步工作,如下所述:Synchronous and Asynchronous Operations,使用 [OperationContract(AsyncPattern=true)]

【讨论】:

Ehr.. 你能详细说明一下吗?你是说我可以保持服务不变,并在客户端实现异步处理?是的-风险很高,我认为这比它更复杂:-P 很抱歉我的速度很慢,但是 Service.SampleClient 到底是什么?它从哪里获得 AddCompleted 和 AddAsync 函数?【参考方案3】:

在不使用 svcutil 的情况下在客户端实现异步操作的另一种方法是在客户端本地设置一个接口 (ServiceContract) 来实现异步操作。

服务端合约:

[ServiceContract]
public interface IServerContract

    [OperationContract]
    string GetData(int value);
 

客户端的异步合约

[ServiceContract(Namespace = "http://tempuri.org/", Name = "IServerContract")]
 public interface IClientContractAsync
 
      [OperationContract] 
      Task<string> GetDataAsync(int value);
 

请注意,需要在客户端合约上设置名称和默认命名空间,以匹配服务器合约的命名空间。所以现在你有一个异步操作(希望没有启动任何新线程)。这样您就不必在服务器端进行任何特定的实现。当然,这与 SvcUtil 所做的类似,但 SvcUtil 会生成很多额外的代码,有时我会发现 svcutil 会导致问题,例如随着类的重用。

客户端代理

public class ClientProxy : ClientBase<IClientContractAsync>

    public IClientContractAsync ChannelOut
    
        get
        
            return Channel;
        
    

客户端的用法:

static void Main(string[] args)

    var client = new ClientProxy();
    client.Open();
    var asyncReadValue = client.ChannelOut.GetDataAsync(45).Result;
    Console.WriteLine(asyncReadValue);
    Console.ReadLine();

服务器类

public class ServerService : IServerContract

    public string GetData(int value)
    
        return string.Format("You entered: 0", value);
    

服务器主机

static void Main(string[] args)

    var server = new ServiceHost(typeof(ServerService));
    server.Open();
    Console.WriteLine("started");
    Console.ReadLine();

【讨论】:

以上是关于实现异步 WCF 服务的主要内容,如果未能解决你的问题,请参考以下文章

如何异步调用我的 WCF 服务?

异步 WCF 服务超时

WCF服务需要调用异步

在for循环中同步消费异步wcf服务

System.OutOfMemoryException 对 WCF 服务的多个异步调用

如何等待异步 WCF 服务方法完成? [复制]