2

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

问题是,我还在学校攻读学位,对异步编程知之甚少。我试图在网上阅读它(我的雇主甚至订阅了 Books 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 类具有正确的返回类型,这就是我从调用中获取数据的方式。我似乎无法弄清楚如何实际做到这一点。

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

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

  2. 如何确保调用实际上是异步发生的?我记得有一次读到,简单地调用异步 SOAP 方法实际上并不会与当前线程异步发生,除非您先执行其他操作。有小费吗?

  3. 还有其他我遗漏的主要考虑因素吗?(如:“如果你忘记这样做,它会炸毁你的程序!”)

这些都是迄今为止我无法找到令人信服的确切答案的问题。预先感谢您提供的任何帮助。

4

2 回答 2

4
  1. You need to handle the getRecordsCompleted event on the proxy which was auto-generated for you, like so:

    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. If you use an auto-generated method on the proxy which ends with Async, the call will be made asynchronously - and that's it. What it sounds to me that you need to prove is that the call is non-blocking (that is, the UI thread does not have to wait for it to complete), and that's a bit tricky as you can't really inject custom logic into the auto-generated code. A synchronous call made from a UI thread will block the UI and your application will become unresponsive. If that's not happening and your UI still responds to button clicks, keyboard events etc while the web service is running, you can be sure that the call is non-blocking. Obviously this will be tricky to prove if your web service call returns quickly.

  3. You're not showing any client code so it's hard to say if you're missing anything.

于 2013-05-31T16:04:42.323 回答
1

For point 1

I think you are missing something on the code you are showing. Maybe the definition of getRecordsCompleted? It may be of type event I suppose, so you can attach a handler of type getRecordsCompletedEventHandler to your event so you can do something with the result of your asynchronous call.

Let's say your client proxy class name is 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
  }
}

[Edit]

For point 2

If you are generating your client proxy with svcutil (Visual Studio > add Service reference) you can trust in it :) or you can watch the involved Threads with the Visual Studio Thread window.

For point 3

You might have some Thread synchronization problems, for example if you update some UI components in another Thread than the UI thread where they belong to. So you may need to do some extra work (dispatch).

于 2013-05-31T15:42:18.623 回答