简体   繁体   中英

Need help implementing async calls in C# 4.0 Web Service Client

So I'm working on a client that consumes a web service. I used the WSDL and XSD files from the service to generate the proxy class, and all of the synchronous functions work fine. However, given their synchronous nature, making any of the calls causes the UI to stop responding until the call is finished. Classic reason for using async methods, right?

Problem is, I'm still in school for my degree and know little about asynchronous programming. I've tried to read up on it online (my employer even has a Books 24x7 subscription) but I'm having a hard time grasping how I should make the calls and how to handle the response. Here's what I have:

    /// <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));
        }
    }

There's also this:

/// <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]));
        }
    }
}

and this:

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

I chose this example because the synchronous call has a return type and the async does not--at least not in the function call itself. I understand that the getRecordsCompletedEventArgs class has the proper return type, and that that is how I will get the data back from the call. What I can't seem to figure out is how to actually do that.

Let's say that I replace my current call to getRecords with getRecordsAsync:

  1. How do I set up the client to respond when the async call completes? I need to drop the XML into a file using a LINQ procedure I've already written, I need to log the operation's success or failure, and I need to notify the user that the operation completed.

  2. How can I ensure that making the call actually happens asynchronously? I remember reading at one point that simply invoking an asynchronous SOAP method doesn't actually happen asynchronously with regard to the current thread unless you do something else first. Any tips?

  3. Are there any other major considerations that I'm missing? (Such as: "If you forget to do this, it'll blow up your program!")

These are all questions that I haven't been able to find convincingly firm answers to so far. Thank you in advance for any help you all can offer.

  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.

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 ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM