需要帮助在C#4.0 Web Service Client中实现异步调用

时间:2013-05-31 12:54:29

标签: c# wcf asynchronous

所以我正在使用一个使用Web服务的客户端。我使用服务中的WSDL和XSD文件生成代理类,并且所有同步函数都可以正常工作。但是,鉴于它们的同步特性,进行任何调用都会导致UI停止响应,直到调用完成。使用异步方法的经典原因,对吧?

问题是,我还在学校攻读学位,对异步编程知之甚少。我试图在网上阅读它(我的雇主甚至有一本24x7的书籍订阅),但我很难掌握如何拨打电话以及如何处理响应。这就是我所拥有的:

    /// <remarks/>
    [System.Web.Services.Protocols.SoapDocumentMethodAttribute("http://localhost:8080/getRecords", RequestNamespace="http://www.<redacted>.com/ws/schemas", ResponseNamespace="http://www.<redacted>.com/ws/schemas", Use=System.Web.Services.Description.SoapBindingUse.Literal, ParameterStyle=System.Web.Services.Protocols.SoapParameterStyle.Wrapped)]
    [return: System.Xml.Serialization.XmlArrayAttribute("records", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)]
    [return: System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=false)]
    public record[] getRecords([System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] string username, [System.Xml.Serialization.XmlArrayAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, IsNullable=true)] [System.Xml.Serialization.XmlArrayItemAttribute("list", Form=System.Xml.Schema.XmlSchemaForm.Unqualified, DataType="integer", IsNullable=false)] string[] ids) {
        object[] results = this.Invoke("getRecords", new object[] {
                    username,
                    ids});
        return ((record[])(results[0]));
    }

    /// <remarks/>
    public void getRecordsAsync(string username, string[] ids) {
        this.getRecordsAsync(username, ids, null);
    }

    /// <remarks/>
    public void getRecordsAsync(string username, string[] ids, object userState) {
        if ((this.getRecordsOperationCompleted == null)) {
            this.getRecordsOperationCompleted = new System.Threading.SendOrPostCallback(this.OngetRecordsOperationCompleted);
        }
        this.InvokeAsync("getRecords", new object[] {
                    username,
                    ids}, this.getRecordsOperationCompleted, userState);
    }

    private void OngetRecordsOperationCompleted(object arg) {
        if ((this.getRecordsCompleted != null)) {
            System.Web.Services.Protocols.InvokeCompletedEventArgs invokeArgs = ((System.Web.Services.Protocols.InvokeCompletedEventArgs)(arg));
            this.getRecordsCompleted(this, new getRecordsCompletedEventArgs(invokeArgs.Results, invokeArgs.Error, invokeArgs.Cancelled, invokeArgs.UserState));
        }
    }

还有这个:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.1")]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
public partial class getRecordsCompletedEventArgs : System.ComponentModel.AsyncCompletedEventArgs {

    private object[] results;

    internal getRecordsCompletedEventArgs(object[] results, System.Exception exception, bool cancelled, object userState) : 
            base(exception, cancelled, userState) {
        this.results = results;
    }

    /// <remarks/>
    public record[] Result {
        get {
            this.RaiseExceptionIfNecessary();
            return ((record[])(this.results[0]));
        }
    }
}

和此:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Web.Services", "4.0.30319.1")]
public delegate void getRecordsCompletedEventHandler(object sender, getRecordsCompletedEventArgs e);

我之所以选择此示例是因为同步调用具有返回类型而异步不具有 - 至少在函数调用本身中没有。我知道getRecordsCompletedEventArgs类具有正确的返回类型,这就是我将如何从调用中获取数据。我似乎无法弄清楚的是如何实际做到这一点。

假设我用getRecordsAsync替换当前对getRecords的调用:

  1. 如何设置客户端在异步调用完成时进行响应?我需要使用我已编写的LINQ过程将XML删除到文件中,我需要记录操作的成功或失败,我需要通知用户操作已完成。

  2. 如何确保调用实际上是异步发生的?我记得曾经读过一点,简单地调用异步SOAP方法实际上并不是异步发生在当前线程上除非你先做别的事。有什么提示吗?

  3. 我还缺少其他重要注意事项吗?(例如:“如果你忘记这样做,它会炸毁你的程序!”)

  4. 到目前为止,这些都是我无法找到令人信服的答案的问题。提前感谢您提供的所有帮助。

2 个答案:

答案 0 :(得分:4)

  1. 您需要处理为您自动生成的代理上的getRecordsCompleted事件,如下所示:

    private void Button_Click(object sender, EventArgs e)
    {
        var proxy = new WebServiceProxy();
    
        // Tell the proxy object that when the web service
        // call completes we want it to invoke our custom
        // handler which will process the result for us.
        proxy.getRecordsCompleted += this.HandleGetRecordsCompleted;
    
        // Make the async call. The UI thread will not wait for 
        // the web service call to complete. This method will
        // return almost immediately while the web service
        // call is happening in the background.
        // Think of it as "scheduling" a web service
        // call as opposed to actually waiting for it
        // to finish before this method can progress.
        proxy.getRecordsAsync("USERNAME", new[] { 1, 2, 3, 4 });
    
        this.Button.Enabled = false;
    }
    
    /// <summary>
    /// Handler for when the web service call returns.
    /// </summary>
    private void HandleGetRecordsCompleted(object sender, getRecordsCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            MessageBox.Show(e.Error.ToString());
        }
        else
        {
            record[] result = e.Result;
    
            // Run your LINQ code on the result here.
        }
    
        this.Button.Enabled = true;
    }
    
  2. 如果在代理上使用以Async结尾的自动生成方法,则会异步调用 - 就是这样。对我来说,你需要证明的是,该调用是非阻塞(也就是说,UI线程不必等待它完成),这对你来说有点棘手无法真正将自定义逻辑注入自动生成的代码中。 从UI线程进行的同步调用将阻止UI,您的应用程序将无响应。如果没有发生这种情况,并且在Web服务运行时您的UI仍然响应按钮单击,键盘事件等,您可以确保该呼叫是非阻塞的。显然,要证明您的Web服务调用是否快速返回将会很棘手。

  3. 您没有显示任何客户端代码,因此很难说您是否遗漏了任何内容。

答案 1 :(得分:1)

对于第1点

我认为你遗漏了你正在展示的代码。也许是getRecordsCompleted的定义?我认为它可能是event类型,因此您可以将getRecordsCompletedEventHandler类型的处理程序附加到event,这样您就可以对自己的结果执行某些操作异步调用。

假设您的客户端代理类名称为RecordCleint

RecordClient client = new RecordClient();
//attaching an event handler
client.getRecordsCompleted += onGetRecordsCompleted;
//calling the web service asynchronously
client.getRecordsAsync("username", [ids]);

//definition of onGetRecordsCompleted of type getRecordsCompletedEventHandler
private void onGetRecordsCompleted(object sender, getRecordsCompletedEventArgs e)
{
  if(e.Error != null)
  {
    record[] data = e.Result;
    //do something with your data
  }
  else
  {
    //handle error
  }
}

<强> [编辑]

第2点

如果您使用 svcutil 生成客户端代理(Visual Studio&gt;添加服务引用),您可以信赖它:)或者您可以使用Visual Studio Thread观看所涉及的线程窗口

第3点

您可能遇到一些线程同步问题,例如,如果您更新另一个线程中的某些UI组件而不是它们所属的UI线程。所以你可能需要做一些额外的工作( dispatch )。